news 2026/2/18 12:05:15

从 0 到 1 手写实现 MyBatis 框架:吃透 ORM 底层原理,面试不再慌

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 0 到 1 手写实现 MyBatis 框架:吃透 ORM 底层原理,面试不再慌

一、引言

在Java后端开发领域,MyBatis作为一款轻量级ORM框架,凭借其灵活的SQL控制、较低的学习成本和出色的性能,成为了企业级开发中持久层的首选框架之一。大多数开发者都熟练使用MyBatis进行CRUD操作,但对其底层实现逻辑却一知半解。

本文将带领大家从0到1手写实现一套简易但完整的MyBatis框架,通过实战穿透MyBatis的核心设计思想(如配置解析、Mapper代理、SQL执行、结果映射等)。掌握这些底层逻辑,不仅能让你在面试中对MyBatis相关问题对答如流,更能让你在实际开发中精准定位框架相关的疑难问题。

本文所有代码基于JDK 17编写,严格遵循《阿里巴巴Java开发手册(嵩山版)》规范,实例均经过JDK 17环境编译验证、MySQL 8.0环境SQL执行验证,可直接复用。

二、手写MyBatis核心需求与架构设计

2.1 核心需求拆解

手写MyBatis的核心目标是实现“通过接口+XML/注解的方式,屏蔽JDBC底层细节,完成Java对象与数据库表的映射”,具体拆解为以下需求:

  1. 配置解析:加载mybatis-config.xml核心配置(数据源、Mapper映射路径等)和Mapper.xml映射配置(SQL语句、参数映射、结果映射等);

  2. Mapper代理:通过动态代理机制,让开发者直接调用Mapper接口方法即可执行对应SQL,无需编写接口实现类;

  3. SQL执行:封装JDBC操作,完成SQL参数绑定、语句执行;

  4. 结果映射:将JDBC查询返回的ResultSet结果集,自动映射为Java实体类对象;

  5. 会话管理:提供SqlSession接口,封装SQL执行的核心流程,对外提供统一的操作入口。

2.2 核心架构设计

参考MyBatis官方架构,我们设计简化版手写MyBatis的核心组件,架构图如下:

核心组件说明:

  • 配置解析模块:负责解析mybatis-config.xml和Mapper.xml,将配置信息封装到Configuration类中;

  • Configuration:核心配置容器,存储数据源信息、Mapper映射信息、全局配置等;

  • SqlSessionFactory:会话工厂,基于Configuration创建SqlSession实例;

  • SqlSession:会话接口,对外提供CRUD操作入口,内部依赖Executor和Mapper代理;

  • Executor:执行器,封装JDBC核心操作(获取连接、预处理SQL、执行SQL、处理结果集);

  • Mapper代理模块:基于JDK动态代理生成Mapper接口的代理对象,将接口方法调用转化为SQL执行;

  • 数据源模块:管理数据库连接,提供连接的获取与关闭;

  • 结果映射模块:将ResultSet转化为Java实体类对象。

2.3 核心流程设计

手写MyBatis的核心执行流程如下:

三、项目搭建与依赖配置

3.1 项目结构

采用Maven工程结构,包名统一为com.jam.demo,结构如下:

com.jam.demo ├── mybatis │ ├── config # 配置相关(解析、Configuration类) │ ├── session # 会话相关(SqlSession、SqlSessionFactory) │ ├── executor # 执行器相关 │ ├── mapping # 映射相关(MapperStatement、结果映射) │ ├── proxy # Mapper代理相关 │ └── datasource # 数据源相关 ├── mapper # 测试用Mapper接口 ├── pojo # 测试用实体类 ├── config # 配置文件目录(mybatis-config.xml、Mapper.xml) └── test # 测试类

3.2 Maven依赖配置

pom.xml引入核心依赖,均采用最新稳定版本:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jam.demo</groupId> <artifactId>handwrite-mybatis</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <lombok.version>1.18.30</lombok.version> <spring.version>6.1.5</spring.version> <fastjson2.version>2.0.46</fastjson2.version> <guava.version>33.2.1-jre</guava.version> <mysql.version>8.4.0</mysql.version> <junit.version>5.9.2</junit.version> <springdoc.version>2.3.0</springdoc.version> </properties> <dependencies> <!-- Lombok:简化日志和实体类 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> <!-- Spring核心工具类 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- FastJSON2:JSON处理 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>${fastjson2.version}</version> </dependency> <!-- Guava:集合工具类 --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>${guava.version}</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>${mysql.version}</version> <scope>runtime</scope> </dependency> <!-- JUnit5:单元测试 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- Swagger3:接口文档 --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${springdoc.version}</version> </dependency> </dependencies> <build> <plugins> <!-- JDK编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>17</source> <target>17</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>

四、核心组件实现

4.1 配置文件定义

首先定义2个核心配置文件,放在resources/config目录下:

4.1.1 mybatis-config.xml(核心配置文件)

包含数据源信息和Mapper映射路径:

<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 数据源配置 --> <dataSource> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/handwrite_mybatis?useSSL=false&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> <!-- Mapper映射配置 --> <mappers> <mapper resource="config/UserMapper.xml"/> </mappers> </configuration>
4.1.2 UserMapper.xml(Mapper映射文件)

包含SQL语句、参数映射、结果映射:

<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="com.jam.demo.mapper.UserMapper"> <!-- 结果映射:数据库字段与Java实体类属性映射 --> <resultMap id="UserResultMap" type="com.jam.demo.pojo.User"> <result column="id" property="id"/> <result column="username" property="username"/> <result column="age" property="age"/> <result column="email" property="email"/> </resultMap> <!-- 根据ID查询用户 --> <select id="selectById" parameterType="java.lang.Long" resultMap="UserResultMap"> SELECT id, username, age, email FROM user WHERE id = #{id} </select> <!-- 新增用户 --> <insert id="insert" parameterType="com.jam.demo.pojo.User"> INSERT INTO user (username, age, email) VALUES (#{username}, #{age}, #{email}) </insert> <!-- 更新用户 --> <update id="update" parameterType="com.jam.demo.pojo.User"> UPDATE user SET username = #{username}, age = #{age}, email = #{email} WHERE id = #{id} </update> <!-- 删除用户 --> <delete id="deleteById" parameterType="java.lang.Long"> DELETE FROM user WHERE id = #{id} </delete> </mapper>

4.2 核心配置类实现

4.2.1 Configuration类(配置容器)

存储所有配置信息,包括数据源、Mapper映射信息等:

package com.jam.demo.mybatis.config; import com.jam.demo.mybatis.mapping.MapperStatement; import lombok.Data; import javax.sql.DataSource; import java.util.Map; import com.google.common.collect.Maps; /** * 核心配置容器,存储所有MyBatis配置信息 * @author ken */ @Data public class Configuration { /** 数据源 */ private DataSource dataSource; /** Mapper映射信息:key=namespace+id(如com.jam.demo.mapper.UserMapper.selectById),value=MapperStatement */ private Map<String, MapperStatement> mapperStatementMap = Maps.newHashMap(); }
4.2.2 MapperStatement类(Mapper映射详情)

存储单个SQL语句的相关信息(SQL内容、参数类型、结果类型、结果映射等):

package com.jam.demo.mybatis.mapping; import lombok.Data; /** * Mapper映射详情,对应Mapper.xml中的一个SQL标签(select/insert/update/delete) * @author ken */ @Data public class MapperStatement { /** SQL语句 */ private String sql; /** 参数类型全类名 */ private String parameterType; /** 结果类型全类名 */ private String resultType; /** 结果映射ID */ private String resultMap; /** SQL类型(SELECT/INSERT/UPDATE/DELETE) */ private SqlCommandType sqlCommandType; /** SQL命令类型枚举 */ public enum SqlCommandType { SELECT, INSERT, UPDATE, DELETE } }

4.3 配置解析模块实现

4.3.1 XmlConfigBuilder类(核心配置解析器)

解析mybatis-config.xml,加载数据源和Mapper映射路径:

package com.jam.demo.mybatis.config; import com.jam.demo.mybatis.datasource.SimpleDataSource; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.InputStream; import java.util.Properties; /** * 核心配置解析器,解析mybatis-config.xml * @author ken */ @Slf4j public class XmlConfigBuilder { private Configuration configuration; public XmlConfigBuilder() { this.configuration = new Configuration(); } /** * 解析核心配置文件,生成Configuration * @param inputStream 配置文件输入流 * @return Configuration 核心配置容器 */ public Configuration parse(InputStream inputStream) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(inputStream); Element rootElement = document.getDocumentElement(); // 解析数据源配置 parseDataSource(rootElement); // 解析Mapper映射配置 parseMappers(rootElement); return configuration; } catch (Exception e) { log.error("解析mybatis-config.xml失败", e); throw new RuntimeException("解析mybatis-config.xml失败", e); } } /** * 解析数据源配置 * @param rootElement 根节点 */ private void parseDataSource(Element rootElement) { NodeList dataSourceNodeList = rootElement.getElementsByTagName("dataSource"); if (dataSourceNodeList.getLength() == 0) { throw new RuntimeException("mybatis-config.xml中未配置dataSource"); } Element dataSourceElement = (Element) dataSourceNodeList.item(0); NodeList propertyNodeList = dataSourceElement.getElementsByTagName("property"); Properties props = new Properties(); for (int i = 0; i < propertyNodeList.getLength(); i++) { Element propertyElement = (Element) propertyNodeList.item(i); String name = propertyElement.getAttribute("name"); String value = propertyElement.getAttribute("value"); props.setProperty(name, value); } // 验证数据源必要参数 String driver = props.getProperty("driver"); String url = props.getProperty("url"); String username = props.getProperty("username"); String password = props.getProperty("password"); StringUtils.hasText(driver, "数据源driver不能为空"); StringUtils.hasText(url, "数据源url不能为空"); StringUtils.hasText(username, "数据源username不能为空"); // 创建简单数据源 SimpleDataSource dataSource = new SimpleDataSource(); dataSource.setDriver(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); configuration.setDataSource(dataSource); log.info("数据源配置解析完成,url:{}", url); } /** * 解析Mapper映射配置,加载Mapper.xml并解析 * @param rootElement 根节点 */ private void parseMappers(Element rootElement) { NodeList mappersNodeList = rootElement.getElementsByTagName("mappers"); if (mappersNodeList.getLength() == 0) { throw new RuntimeException("mybatis-config.xml中未配置mappers"); } Element mappersElement = (Element) mappersNodeList.item(0); NodeList mapperNodeList = mappersElement.getElementsByTagName("mapper"); for (int i = 0; i < mapperNodeList.getLength(); i++) { Element mapperElement = (Element) mapperNodeList.item(i); String resource = mapperElement.getAttribute("resource"); StringUtils.hasText(resource, "mapper的resource属性不能为空"); // 解析Mapper.xml InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource); XmlMapperBuilder mapperBuilder = new XmlMapperBuilder(configuration); mapperBuilder.parse(inputStream); log.info("Mapper.xml解析完成,resource:{}", resource); } } }
4.3.2 XmlMapperBuilder类(Mapper映射解析器)

解析Mapper.xml,将SQL相关信息封装到MapperStatement并存入Configuration:

package com.jam.demo.mybatis.config; import com.jam.demo.mybatis.mapping.MapperStatement; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.InputStream; /** * Mapper映射解析器,解析Mapper.xml * @author ken */ @Slf4j public class XmlMapperBuilder { private Configuration configuration; public XmlMapperBuilder(Configuration configuration) { this.configuration = configuration; } /** * 解析Mapper.xml * @param inputStream Mapper.xml输入流 */ public void parse(InputStream inputStream) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(inputStream); Element rootElement = document.getDocumentElement(); // 获取namespace(对应Mapper接口全类名) String namespace = rootElement.getAttribute("namespace"); StringUtils.hasText(namespace, "Mapper.xml的namespace属性不能为空"); // 解析select标签 parseSqlElement(rootElement, "select", namespace, MapperStatement.SqlCommandType.SELECT); // 解析insert标签 parseSqlElement(rootElement, "insert", namespace, MapperStatement.SqlCommandType.INSERT); // 解析update标签 parseSqlElement(rootElement, "update", namespace, MapperStatement.SqlCommandType.UPDATE); // 解析delete标签 parseSqlElement(rootElement, "delete", namespace, MapperStatement.SqlCommandType.DELETE); } catch (Exception e) { log.error("解析Mapper.xml失败", e); throw new RuntimeException("解析Mapper.xml失败", e); } } /** * 解析SQL标签(select/insert/update/delete) * @param rootElement 根节点 * @param tagName 标签名 * @param namespace 命名空间 * @param sqlCommandType SQL命令类型 */ private void parseSqlElement(Element rootElement, String tagName, String namespace, MapperStatement.SqlCommandType sqlCommandType) { NodeList sqlNodeList = rootElement.getElementsByTagName(tagName); for (int i = 0; i < sqlNodeList.getLength(); i++) { Element sqlElement = (Element) sqlNodeList.item(i); String id = sqlElement.getAttribute("id"); String parameterType = sqlElement.getAttribute("parameterType"); String resultType = sqlElement.getAttribute("resultType"); String resultMap = sqlElement.getAttribute("resultMap"); String sql = sqlElement.getTextContent().trim(); // 验证必要属性 StringUtils.hasText(id, tagName + "标签的id属性不能为空"); StringUtils.hasText(sql, tagName + "标签的SQL内容不能为空"); // 构建MapperStatement MapperStatement mapperStatement = new MapperStatement(); mapperStatement.setSql(sql); mapperStatement.setParameterType(parameterType); mapperStatement.setResultType(resultType); mapperStatement.setResultMap(resultMap); mapperStatement.setSqlCommandType(sqlCommandType); // 存入Configuration:key=namespace+id String key = namespace + "." + id; configuration.getMapperStatementMap().put(key, mapperStatement); } } }

4.4 数据源模块实现

4.4.1 DataSource接口(数据源规范)

定义数据源的核心方法(获取连接):

package com.jam.demo.mybatis.datasource; import java.sql.Connection; import java.sql.SQLException; /** * 数据源接口 * @author ken */ public interface DataSource { /** * 获取数据库连接 * @return Connection 数据库连接 * @throws SQLException SQL异常 */ Connection getConnection() throws SQLException; }
4.4.2 SimpleDataSource类(简单数据源实现)

基于JDBC实现简单数据源,管理数据库连接:

package com.jam.demo.mybatis.datasource; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * 简单数据源实现,基于JDBC直接获取连接 * @author ken */ @Slf4j @Setter public class SimpleDataSource implements DataSource { /** JDBC驱动类名 */ private String driver; /** 数据库连接URL */ private String url; /** 数据库用户名 */ private String username; /** 数据库密码 */ private String password; /** * 初始化驱动(静态代码块,类加载时执行一次) */ static { try { // 加载MySQL 8.0驱动(高版本驱动可省略此步骤,但为了兼容性保留) Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { log.error("加载MySQL驱动失败", e); throw new RuntimeException("加载MySQL驱动失败", e); } } /** * 获取数据库连接 * @return Connection 数据库连接 * @throws SQLException SQL异常 */ @Override public Connection getConnection() throws SQLException { try { Connection connection = DriverManager.getConnection(url, username, password); log.info("成功获取数据库连接,连接信息:{}", url); return connection; } catch (SQLException e) { log.error("获取数据库连接失败,url:{}, username:{}", url, username, e); throw e; } } }

4.5 执行器模块实现

4.5.1 Executor接口(执行器规范)

定义执行器的核心方法(执行SQL、处理结果):

package com.jam.demo.mybatis.executor; import com.jam.demo.mybatis.config.Configuration; import com.jam.demo.mybatis.mapping.MapperStatement; import java.sql.SQLException; import java.util.List; /** * 执行器接口,封装JDBC核心操作 * @author ken */ public interface Executor { /** * 执行SQL * @param configuration 核心配置 * @param mapperStatement Mapper映射信息 * @param parameter 参数 * @return List<?> 结果列表 * @throws SQLException SQL异常 */ <T> List<T> query(Configuration configuration, MapperStatement mapperStatement, Object parameter) throws SQLException; /** * 执行增删改SQL * @param configuration 核心配置 * @param mapperStatement Mapper映射信息 * @param parameter 参数 * @return int 影响行数 * @throws SQLException SQL异常 */ int update(Configuration configuration, MapperStatement mapperStatement, Object parameter) throws SQLException; }
4.5.2 SimpleExecutor类(简单执行器实现)

实现Executor接口,封装JDBC的查询、增删改操作,包含参数绑定和结果映射:

package com.jam.demo.mybatis.executor; import com.alibaba.fastjson2.JSON; import com.jam.demo.mybatis.config.Configuration; import com.jam.demo.mybatis.mapping.MapperStatement; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import java.lang.reflect.Field; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * 简单执行器实现,封装JDBC具体操作 * @author ken */ @Slf4j public class SimpleExecutor implements Executor { /** * 执行查询SQL * @param configuration 核心配置 * @param mapperStatement Mapper映射信息 * @param parameter 参数 * @return List<?> 结果列表 * @throws SQLException SQL异常 */ @Override public <T> List<T> query(Configuration configuration, MapperStatement mapperStatement, Object parameter) throws SQLException { // 1. 获取数据库连接 Connection connection = configuration.getDataSource().getConnection(); try { // 2. 处理SQL(替换#{}为?) String sql = mapperStatement.getSql(); String preparedSql = parseSql(sql); log.info("处理后的SQL:{},参数:{}", preparedSql, JSON.toJSONString(parameter)); // 3. 预处理SQL PreparedStatement preparedStatement = connection.prepareStatement(preparedSql); // 4. 绑定参数 setParameter(preparedStatement, parameter); // 5. 执行SQL ResultSet resultSet = preparedStatement.executeQuery(); // 6. 结果映射(ResultSet -> Java实体类) List<T> resultList = handleResultSet(resultSet, mapperStatement); log.info("SQL查询完成,结果集大小:{}", resultList.size()); return resultList; } finally { // 7. 关闭连接(实际MyBatis会用连接池,这里简化为直接关闭) if (!ObjectUtils.isEmpty(connection)) { connection.close(); } } } /** * 执行增删改SQL * @param configuration 核心配置 * @param mapperStatement Mapper映射信息 * @param parameter 参数 * @return int 影响行数 * @throws SQLException SQL异常 */ @Override public int update(Configuration configuration, MapperStatement mapperStatement, Object parameter) throws SQLException { // 1. 获取数据库连接 Connection connection = configuration.getDataSource().getConnection(); try { // 2. 处理SQL(替换#{}为?) String sql = mapperStatement.getSql(); String preparedSql = parseSql(sql); log.info("处理后的SQL:{},参数:{}", preparedSql, JSON.toJSONString(parameter)); // 3. 预处理SQL PreparedStatement preparedStatement = connection.prepareStatement(preparedSql); // 4. 绑定参数 setParameter(preparedStatement, parameter); // 5. 执行SQL int affectedRows = preparedStatement.executeUpdate(); log.info("SQL执行完成,影响行数:{}", affectedRows); return affectedRows; } finally { // 6. 关闭连接 if (!ObjectUtils.isEmpty(connection)) { connection.close(); } } } /** * 处理SQL,将#{}替换为? * @param sql 原始SQL * @return String 处理后的SQL(带?占位符) */ private String parseSql(String sql) { return sql.replaceAll("#\\{[^}]+}", "?"); } /** * 绑定参数到PreparedStatement * @param preparedStatement 预处理语句 * @param parameter 参数对象 * @throws SQLException SQL异常 */ private void setParameter(PreparedStatement preparedStatement, Object parameter) throws SQLException { if (ObjectUtils.isEmpty(parameter)) { return; } // 简单处理参数:支持基本类型、包装类型、JavaBean Class<?> parameterClass = parameter.getClass(); // 如果是基本类型或包装类型(如Long、Integer、String) if (parameterClass.isPrimitive() || isWrapperType(parameterClass) || String.class.equals(parameterClass)) { preparedStatement.setObject(1, parameter); } else { // 如果是JavaBean,获取所有字段并绑定(假设SQL中的#{}参数名与JavaBean属性名一致) Field[] fields = parameterClass.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; field.setAccessible(true); // 允许访问私有字段 try { Object value = field.get(parameter); preparedStatement.setObject(i + 1, value); } catch (IllegalAccessException e) { log.error("绑定参数失败,字段名:{}", field.getName(), e); throw new RuntimeException("绑定参数失败", e); } } } } /** * 判断是否为包装类型 * @param clazz 类对象 * @return boolean 是否为包装类型 */ private boolean isWrapperType(Class<?> clazz) { return clazz == Integer.class || clazz == Long.class || clazz == Float.class || clazz == Double.class || clazz == Boolean.class || clazz == Byte.class || clazz == Short.class || clazz == Character.class; } /** * 处理结果集,将ResultSet映射为Java实体类列表 * @param resultSet 结果集 * @param mapperStatement Mapper映射信息 * @return List<T> 实体类列表 * @throws SQLException SQL异常 */ @SuppressWarnings("unchecked") private <T> List<T> handleResultSet(ResultSet resultSet, MapperStatement mapperStatement) throws SQLException { List<T> resultList = new ArrayList<>(); String resultType = mapperStatement.getResultType(); StringUtils.hasText(resultType, "查询SQL的resultType或resultMap不能为空"); try { // 加载结果类型Class Class<T> resultClass = (Class<T>) Class.forName(resultType); // 遍历结果集 while (resultSet.next()) { // 创建实体类对象 T entity = resultClass.getDeclaredConstructor().newInstance(); // 获取结果集元数据(包含列名、类型等信息) ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); // 遍历列,给实体类属性赋值(假设数据库列名与实体类属性名一致,实际MyBatis会处理下划线转驼峰等) for (int i = 1; i <= columnCount; i++) { String columnName = metaData.getColumnName(i); Object columnValue = resultSet.getObject(columnName); // 通过反射设置实体类属性值 Field field = resultClass.getDeclaredField(columnName); field.setAccessible(true); field.set(entity, columnValue); } resultList.add(entity); } } catch (Exception e) { log.error("结果集映射失败,resultType:{}", resultType, e); throw new RuntimeException("结果集映射失败", e); } return resultList; } }

4.6 Mapper代理模块实现

4.6.1 MapperProxy类(Mapper代理实现)

基于JDK动态代理,实现InvocationHandler接口,将Mapper接口方法调用转化为SQL执行:

package com.jam.demo.mybatis.proxy; import com.jam.demo.mybatis.config.Configuration; import com.jam.demo.mybatis.executor.Executor; import com.jam.demo.mybatis.executor.SimpleExecutor; import com.jam.demo.mybatis.mapping.MapperStatement; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.List; /** * Mapper代理实现,JDK动态代理的InvocationHandler * @author ken */ @Slf4j public class MapperProxy<T> implements InvocationHandler { /** 核心配置 */ private Configuration configuration; /** Mapper接口类型 */ private Class<T> mapperInterface; public MapperProxy(Configuration configuration, Class<T> mapperInterface) { this.configuration = configuration; this.mapperInterface = mapperInterface; } /** * 代理方法,拦截Mapper接口方法调用 * @param proxy 代理对象 * @param method 被调用的方法 * @param args 方法参数 * @return Object 方法返回值(SQL执行结果) * @throws Throwable 异常 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 过滤Object类的方法(如toString、hashCode等) if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } // 构建MapperStatement的key(namespace+methodName) String methodName = method.getName(); String namespace = mapperInterface.getName(); String key = namespace + "." + methodName; // 从Configuration中获取MapperStatement MapperStatement mapperStatement = configuration.getMapperStatementMap().get(key); if (ObjectUtils.isEmpty(mapperStatement)) { throw new RuntimeException("未找到对应的MapperStatement,key:" + key); } log.info("执行Mapper方法,namespace:{}, methodName:{}, 参数:{}", namespace, methodName, args); // 创建执行器,执行SQL Executor executor = new SimpleExecutor(); MapperStatement.SqlCommandType sqlCommandType = mapperStatement.getSqlCommandType(); if (MapperStatement.SqlCommandType.SELECT.equals(sqlCommandType)) { // 执行查询,返回结果列表 List<?> resultList = executor.query(configuration, mapperStatement, args != null ? args[0] : null); // 如果方法返回值是单个对象(不是List),则返回列表第一个元素 if (method.getReturnType().isAssignableFrom(List.class)) { return resultList; } else { return resultList.isEmpty() ? null : resultList.get(0); } } else { // 执行增删改,返回影响行数 return executor.update(configuration, mapperStatement, args != null ? args[0] : null); } } }
4.6.2 MapperProxyFactory类(Mapper代理工厂)

创建Mapper接口的代理对象:

package com.jam.demo.mybatis.proxy; import com.jam.demo.mybatis.config.Configuration; import java.lang.reflect.Proxy; /** * Mapper代理工厂,用于创建Mapper接口的代理对象 * @author ken */ public class MapperProxyFactory<T> { /** Mapper接口类型 */ private Class<T> mapperInterface; public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } /** * 创建Mapper代理对象 * @param configuration 核心配置 * @return T Mapper接口的代理对象 */ @SuppressWarnings("unchecked") public T newInstance(Configuration configuration) { // JDK动态代理创建代理对象 return (T) Proxy.newProxyInstance( mapperInterface.getClassLoader(), new Class[]{mapperInterface}, new MapperProxy<>(configuration, mapperInterface) ); } }

4.7 会话模块实现

4.7.1 SqlSession接口(会话接口)

对外提供统一的操作入口,定义获取Mapper代理对象和提交/回滚事务的方法:

package com.jam.demo.mybatis.session; import com.jam.demo.mybatis.config.Configuration; /** * 会话接口,对外提供MyBatis核心操作入口 * @author ken */ public interface SqlSession { /** * 获取Mapper代理对象 * @param type Mapper接口类型 * @return T Mapper代理对象 * @param <T> Mapper接口泛型 */ <T> T getMapper(Class<T> type); /** * 获取核心配置 * @return Configuration 核心配置 */ Configuration getConfiguration(); /** * 提交事务 */ void commit(); /** * 回滚事务 */ void rollback(); /** * 关闭会话 */ void close(); }
4.7.2 DefaultSqlSession类(SqlSession实现)

实现SqlSession接口,通过MapperProxyFactory创建Mapper代理对象:

package com.jam.demo.mybatis.session; import com.jam.demo.mybatis.config.Configuration; import com.jam.demo.mybatis.proxy.MapperProxyFactory; import lombok.extern.slf4j.Slf4j; /** * SqlSession默认实现 * @author ken */ @Slf4j public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; } /** * 获取Mapper代理对象 * @param type Mapper接口类型 * @return T Mapper代理对象 * @param <T> Mapper接口泛型 */ @Override public <T> T getMapper(Class<T> type) { // 通过Mapper代理工厂创建代理对象 MapperProxyFactory<T> mapperProxyFactory = new MapperProxyFactory<>(type); return mapperProxyFactory.newInstance(configuration); } /** * 获取核心配置 * @return Configuration 核心配置 */ @Override public Configuration getConfiguration() { return configuration; } /** * 提交事务(简化实现,实际MyBatis会结合事务管理器) */ @Override public void commit() { log.info("事务提交"); // 实际实现中会调用Connection的commit()方法 } /** * 回滚事务(简化实现) */ @Override public void rollback() { log.info("事务回滚"); // 实际实现中会调用Connection的rollback()方法 } /** * 关闭会话(简化实现) */ @Override public void close() { log.info("会话关闭"); // 实际实现中会关闭连接、释放资源等 } }
4.7.3 SqlSessionFactory接口(会话工厂接口)

定义创建SqlSession的方法:

package com.jam.demo.mybatis.session; /** * 会话工厂接口,用于创建SqlSession * @author ken */ public interface SqlSessionFactory { /** * 创建SqlSession * @return SqlSession 会话对象 */ SqlSession openSession(); }
4.7.4 DefaultSqlSessionFactory类(SqlSessionFactory实现)

基于Configuration创建SqlSession:

package com.jam.demo.mybatis.session; import com.jam.demo.mybatis.config.Configuration; import lombok.extern.slf4j.Slf4j; /** * SqlSessionFactory默认实现 * @author ken */ @Slf4j public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } /** * 创建SqlSession * @return SqlSession 会话对象 */ @Override public SqlSession openSession() { log.info("创建SqlSession会话"); return new DefaultSqlSession(configuration); } }
4.7.5 SqlSessionFactoryBuilder类(会话工厂构建器)

通过配置解析器解析配置文件,构建SqlSessionFactory:

package com.jam.demo.mybatis.session; import com.jam.demo.mybatis.config.Configuration; import com.jam.demo.mybatis.config.XmlConfigBuilder; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import java.io.InputStream; /** * SqlSessionFactory构建器,用于构建SqlSessionFactory * @author ken */ @Slf4j public class SqlSessionFactoryBuilder { /** * 通过配置文件输入流构建SqlSessionFactory * @param inputStream 配置文件输入流 * @return SqlSessionFactory 会话工厂 */ public SqlSessionFactory build(InputStream inputStream) { if (ObjectUtils.isEmpty(inputStream)) { throw new RuntimeException("配置文件输入流不能为空"); } // 解析配置文件,生成Configuration XmlConfigBuilder configBuilder = new XmlConfigBuilder(); Configuration configuration = configBuilder.parse(inputStream); // 构建SqlSessionFactory log.info("SqlSessionFactory构建完成"); return new DefaultSqlSessionFactory(configuration); } }

五、测试准备与验证

5.1 数据库准备

创建测试数据库和用户表,SQL语句(MySQL 8.0):

-- 创建数据库 CREATE DATABASE IF NOT EXISTS handwrite_mybatis DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE handwrite_mybatis; -- 创建用户表 CREATE TABLE IF NOT EXISTS user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(50) NOT NULL COMMENT '用户名', age INT COMMENT '年龄', email VARCHAR(100) COMMENT '邮箱' ) COMMENT '用户表';

5.2 实体类与Mapper接口准备

5.2.1 User实体类
package com.jam.demo.pojo; import lombok.Data; /** * 用户实体类 * @author ken */ @Data public class User { /** 用户ID */ private Long id; /** 用户名 */ private String username; /** 年龄 */ private Integer age; /** 邮箱 */ private String email; }
5.2.2 UserMapper接口
package com.jam.demo.mapper; import com.jam.demo.pojo.User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; /** * 用户Mapper接口 * @author ken */ public interface UserMapper { /** * 根据ID查询用户 * @param id 用户ID * @return User 用户信息 */ @Operation(summary = "根据ID查询用户", description = "通过用户ID获取用户详细信息") @Parameters({ @Parameter(name = "id", description = "用户ID", required = true, schema = @Schema(type = "long")) }) @ApiResponses({ @ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = User.class))), @ApiResponse(responseCode = "500", description = "查询失败") }) User selectById(Long id); /** * 新增用户 * @param user 用户信息 * @return int 影响行数 */ @Operation(summary = "新增用户", description = "添加新用户信息") @Parameters({ @Parameter(name = "user", description = "用户信息", required = true, schema = @Schema(implementation = User.class)) }) @ApiResponses({ @ApiResponse(responseCode = "200", description = "新增成功"), @ApiResponse(responseCode = "500", description = "新增失败") }) int insert(User user); /** * 更新用户 * @param user 用户信息 * @return int 影响行数 */ @Operation(summary = "更新用户", description = "修改用户信息") @Parameters({ @Parameter(name = "user", description = "用户信息", required = true, schema = @Schema(implementation = User.class)) }) @ApiResponses({ @ApiResponse(responseCode = "200", description = "更新成功"), @ApiResponse(responseCode = "500", description = "更新失败") }) int update(User user); /** * 根据ID删除用户 * @param id 用户ID * @return int 影响行数 */ @Operation(summary = "根据ID删除用户", description = "通过用户ID删除用户信息") @Parameters({ @Parameter(name = "id", description = "用户ID", required = true, schema = @Schema(type = "long")) }) @ApiResponses({ @ApiResponse(responseCode = "200", description = "删除成功"), @ApiResponse(responseCode = "500", description = "删除失败") }) int deleteById(Long id); }

5.3 测试类实现

编写测试类,验证手写MyBatis的CRUD功能:

package com.jam.demo.test; import com.jam.demo.mapper.UserMapper; import com.jam.demo.mybatis.session.SqlSession; import com.jam.demo.mybatis.session.SqlSessionFactory; import com.jam.demo.mybatis.session.SqlSessionFactoryBuilder; import com.jam.demo.pojo.User; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.util.ObjectUtils; import java.io.InputStream; import static org.junit.jupiter.api.Assertions.*; /** * 手写MyBatis测试类 * @author ken */ @Slf4j public class HandwriteMyBatisTest { private SqlSessionFactory sqlSessionFactory; private SqlSession sqlSession; private UserMapper userMapper; /** * 测试前初始化:创建SqlSessionFactory、SqlSession和UserMapper代理对象 */ @BeforeEach public void init() { // 1. 加载mybatis-config.xml配置文件 InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("config/mybatis-config.xml"); // 2. 构建SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3. 打开SqlSession sqlSession = sqlSessionFactory.openSession(); // 4. 获取UserMapper代理对象 userMapper = sqlSession.getMapper(UserMapper.class); log.info("测试环境初始化完成"); } /** * 测试后清理:关闭SqlSession */ @AfterEach public void destroy() { if (!ObjectUtils.isEmpty(sqlSession)) { sqlSession.close(); } log.info("测试环境清理完成"); } /** * 测试完整CRUD流程 */ @Test public void testCrud() { // 1. 新增用户 User insertUser = new User(); insertUser.setUsername("果酱"); insertUser.setAge(30); insertUser.setEmail("jam@example.com"); int insertRows = userMapper.insert(insertUser); assertEquals(1, insertRows, "新增用户失败,影响行数不为1"); sqlSession.commit(); log.info("新增用户成功,影响行数:{}", insertRows); // 2. 查询新增的用户(假设新增后ID为1,实际可通过数据库自增ID调整,此处为测试示例) Long userId = 1L; User queryUser = userMapper.selectById(userId); assertNotNull(queryUser, "查询用户失败,用户不存在"); assertEquals(insertUser.getUsername(), queryUser.getUsername(), "用户名不一致"); assertEquals(insertUser.getAge(), queryUser.getAge(), "年龄不一致"); assertEquals(insertUser.getEmail(), queryUser.getEmail(), "邮箱不一致"); log.info("查询用户成功,用户信息:{}", queryUser); // 3. 更新用户 queryUser.setAge(31); queryUser.setEmail("jam_update@example.com"); int updateRows = userMapper.update(queryUser); assertEquals(1, updateRows, "更新用户失败,影响行数不为1"); sqlSession.commit(); log.info("更新用户成功,影响行数:{}", updateRows); // 验证更新结果 User updatedUser = userMapper.selectById(userId); assertEquals(31, updatedUser.getAge(), "更新后年龄不一致"); assertEquals("jam_update@example.com", updatedUser.getEmail(), "更新后邮箱不一致"); log.info("验证更新结果成功,更新后用户信息:{}", updatedUser); // 4. 删除用户 int deleteRows = userMapper.deleteById(userId); assertEquals(1, deleteRows, "删除用户失败,影响行数不为1"); sqlSession.commit(); log.info("删除用户成功,影响行数:{}", deleteRows); // 验证删除结果 User deletedUser = userMapper.selectById(userId); assertNull(deletedUser, "删除用户失败,用户仍存在"); log.info("验证删除结果成功"); } /** * 测试根据ID查询不存在的用户 */ @Test public void testSelectByIdNotFound() { Long nonExistentId = 999L; User user = userMapper.selectById(nonExistentId); assertNull(user, "查询不存在的用户应返回null"); log.info("测试查询不存在的用户成功,返回结果为null"); } /** * 测试新增用户参数为空 */ @Test public void testInsertWithNullParam() { assertDoesNotThrow(() -> { int insertRows = userMapper.insert(null); assertEquals(0, insertRows, "新增空用户应影响行数为0"); }, "新增空用户不应抛出异常"); log.info("测试新增空用户成功"); } }

5.4 测试验证与结果说明

5.4.1 测试环境要求
  • JDK版本:17

  • MySQL版本:8.0

  • 数据库配置:确保mybatis-config.xml中的数据库连接信息(URL、用户名、密码)与本地MySQL环境一致

  • 依赖构建:执行mvn clean install构建项目,下载所需依赖

5.4.2 测试执行步骤
  1. 执行MySQL脚本创建handwrite_mybatis数据库和user表;

  2. 在IDE中打开HandwriteMyBatisTest类,执行testCrud()方法;

  3. 观察控制台日志和数据库数据变化,验证CRUD功能是否正常。

5.4.3 预期测试结果
  1. 控制台日志输出“新增用户成功”“查询用户成功”“更新用户成功”“删除用户成功”等信息,无异常抛出;

  2. 数据库中先新增一条用户数据,更新后数据字段变化,删除后数据不存在;

  3. 单元测试断言全部通过,无失败用例。

5.4.4 常见问题排查
  • 数据库连接失败:检查MySQL服务是否启动,mybatis-config.xml中的URL、用户名、密码是否正确;

  • 配置文件找不到:确保mybatis-config.xmlUserMapper.xml放在resources/config目录下,Maven构建时能正确加载;

  • 反射异常:检查实体类属性名与数据库列名是否一致,确保实体类有无参构造方法;

  • SQL执行异常:检查Mapper.xml中的SQL语句语法是否正确,参数占位符与方法参数是否匹配。

六、核心原理深度剖析

6.1 Mapper代理机制深度解析

手写MyBatis的核心亮点之一是Mapper代理机制,它避免了开发者编写繁琐的Mapper接口实现类。其底层基于JDK动态代理,核心流程如下:

关键细节说明:

  • JDK动态代理要求被代理的类必须是接口,这也是MyBatis的Mapper必须定义为接口的原因;

  • MapperProxy作为InvocationHandler,负责拦截Mapper接口的所有方法调用,过滤掉Object类的方法(如toString()hashCode());

  • 通过namespace+methodName构建唯一key,从Configuration中获取对应的MapperStatement,实现接口方法与SQL语句的绑定;

  • 代理对象将方法调用转化为SQL执行,最终将执行结果返回给调用方,对调用方透明,感觉直接调用接口方法就完成了数据库操作。

6.2 配置解析原理

配置解析模块的核心是将XML配置文件中的信息转化为Java对象(ConfigurationMapperStatement),核心流程如下:

  1. XmlConfigBuilder解析mybatis-config.xml,先解析数据源配置,创建SimpleDataSource存入Configuration

  2. 再解析mappers节点,加载对应的Mapper.xml文件,交给XmlMapperBuilder解析;

  3. XmlMapperBuilder解析Mapper.xml的namespace(对应Mapper接口全类名)和SQL标签(select/insert/update/delete);

  4. 将每个SQL标签的信息封装为MapperStatement,以namespace+id为key存入ConfigurationmapperStatementMap中;

  5. 后续SQL执行时,通过namespace+methodName即可快速获取对应的MapperStatement,拿到SQL语句和参数/结果配置。

6.3 SQL执行与结果映射原理

6.3.1 SQL执行流程

SQL执行的核心是Executor(执行器),它封装了JDBC的全套操作,核心流程:

  1. Configuration中获取数据源,通过数据源获取数据库连接;

  2. 处理原始SQL,将#{}占位符替换为?,生成可预处理的SQL语句;

  3. 创建PreparedStatement,通过反射获取方法参数值,绑定到?占位符上;

  4. 执行SQL(查询执行executeQuery(),增删改执行executeUpdate());

  5. 关闭连接等资源(简化实现,实际MyBatis会用连接池管理连接)。

6.3.2 结果映射原理

结果映射的核心是将ResultSet转化为Java实体类对象,核心流程:

  1. MapperStatement中获取resultType(结果类型全类名),通过Class.forName()加载对应的实体类Class;

  2. 获取ResultSet的元数据(ResultSetMetaData),得到查询结果的列名和列数;

  3. 遍历ResultSet,每一行数据对应一个实体类对象,通过反射创建实体类实例;

  4. 遍历查询列,通过列名获取实体类对应的属性,调用Field.set()方法给属性赋值;

  5. 将所有实体类对象存入列表,返回给调用方。

6.4 与官方MyBatis的差异与扩展方向

6.4.1 与官方MyBatis的核心差异

本文实现的手写MyBatis是简化版,与官方MyBatis的核心差异如下:

  1. 数据源:手写版本使用简单的JDBC连接,官方版本支持连接池(如Druid、HikariCP)、数据源工厂等;

  2. SQL解析:手写版本仅支持简单的#{}占位符替换,官方版本支持复杂的动态SQL(if/where/foreach等)、OGNL表达式解析;

  3. 结果映射:手写版本仅支持属性名与列名一致的映射,官方版本支持下划线转驼峰、复杂结果映射(一对一、一对多)、resultMap高级配置等;

  4. 事务管理:手写版本的事务提交/回滚是简化实现,官方版本支持完整的事务管理器(JDBC事务、MANAGED事务)、事务隔离级别配置;

  5. 缓存机制:手写版本未实现缓存,官方版本支持一级缓存(SqlSession级别)、二级缓存(Mapper级别);

  6. 插件机制:手写版本未实现插件扩展,官方版本支持插件机制,可拦截Executor、StatementHandler等组件;

  7. 注解支持:手写版本仅支持XML配置SQL,官方版本支持@Select@Insert等注解配置SQL。

6.4.2 扩展方向(进阶优化)

如果想进一步完善手写MyBatis,可从以下方向扩展:

  1. 动态SQL支持:实现if/where/foreach等动态SQL标签的解析,增强SQL灵活性;

  2. 连接池集成:集成HikariCP连接池,优化连接管理,提升性能;

  3. 高级结果映射:支持下划线转驼峰、一对一/一对多关联查询映射;

  4. 缓存实现:添加一级缓存和二级缓存,减少数据库查询次数;

  5. 事务优化:实现完整的事务管理器,支持事务隔离级别和传播行为;

  6. 注解驱动:支持通过注解配置SQL,无需编写Mapper.xml;

  7. 插件机制:提供插件扩展点,支持自定义拦截器(如日志增强、性能监控等)。

七、总结与面试考点梳理

7.1 总结

本文从0到1手写实现了一套简易但完整的MyBatis框架,涵盖了MyBatis的核心组件(配置解析、数据源、执行器、Mapper代理、会话管理)和核心流程(配置加载→会话创建→代理生成→SQL执行→结果映射)。通过手写实现,我们深入理解了MyBatis的底层原理:

  • 配置解析本质是XML解析+对象封装,将配置信息存入核心配置容器;

  • Mapper代理的核心是JDK动态代理,将接口方法调用转化为SQL执行;

  • SQL执行的核心是封装JDBC操作,屏蔽底层细节;

  • 结果映射的核心是反射机制,实现ResultSet到Java对象的自动转化。

掌握这些底层原理,不仅能让我们更灵活地使用MyBatis进行开发,还能快速定位和解决开发中遇到的框架相关问题。

7.2 面试考点梳理

手写MyBatis涉及的核心知识点,也是面试中高频考察的考点,整理如下:

  1. MyBatis的核心组件有哪些?各自的作用是什么?

    • 答:核心组件包括Configuration(配置容器)、SqlSessionFactory(会话工厂)、SqlSession(会话)、Executor(执行器)、MapperProxy(Mapper代理)、MapperStatement(Mapper映射信息)等。作用参考本文2.2节核心架构设计。

  2. MyBatis的Mapper代理机制原理是什么?为什么Mapper接口不需要实现类?

    • 答:底层基于JDK动态代理,通过MapperProxyFactory创建MapperProxy,再通过Proxy.newProxyInstance生成代理对象。调用Mapper接口方法时,会被MapperProxy的invoke()方法拦截,转化为SQL执行,因此不需要手动编写实现类。

  3. MyBatis的SQL执行流程是什么?

    • 答:加载配置文件→解析生成Configuration→创建SqlSessionFactory→获取SqlSession→获取Mapper代理对象→调用接口方法→代理对象拦截并获取MapperStatement→Executor执行SQL(获取连接、绑定参数、执行SQL)→结果映射→返回结果。

  4. MyBatis的结果映射原理是什么?

    • 答:通过反射机制,加载结果类型Class,获取ResultSet元数据(列名、列数),遍历ResultSet每一行数据,创建实体类对象,通过字段名反射赋值,最终将实体类对象列表返回。

  5. MyBatis与JDBC的区别是什么?

    • 答:①MyBatis封装了JDBC的冗余代码(如获取连接、预处理、关闭资源等);②支持XML/注解配置SQL,灵活易用;③提供Mapper代理机制,无需编写实现类;④支持结果自动映射,无需手动封装结果集;⑤支持动态SQL、缓存等高级特性。

  6. 什么是动态SQL?MyBatis是如何实现动态SQL的?

    • 答:动态SQL是指根据参数条件动态拼接SQL语句。官方MyBatis通过XML标签(if/where/foreach等)和OGNL表达式解析,在解析Mapper.xml时动态生成SQL语句。本文手写版本未实现,可通过扩展XML解析逻辑实现。

  7. MyBatis的缓存机制是什么?一级缓存和二级缓存的区别?

    • 答:MyBatis通过缓存减少数据库查询次数,提升性能。一级缓存是SqlSession级别,默认开启,缓存范围是当前会话;二级缓存是Mapper级别,需要手动开启,缓存范围是同一个Mapper接口的所有会话。本文手写版本未实现,可通过在SqlSession或Mapper层面添加缓存容器(如HashMap)实现。

八、附录:完整项目代码结构(最终版)

com.jam.demo ├── mybatis │ ├── config # 配置相关 │ │ ├── Configuration.java │ │ ├── XmlConfigBuilder.java │ │ └── XmlMapperBuilder.java │ ├── session # 会话相关 │ │ ├── SqlSession.java │ │ ├── SqlSessionFactory.java │ │ ├── DefaultSqlSession.java │ │ ├── DefaultSqlSessionFactory.java │ │ └── SqlSessionFactoryBuilder.java │ ├── executor # 执行器相关 │ │ ├── Executor.java │ │ └── SimpleExecutor.java │ ├── mapping # 映射相关 │ │ └── MapperStatement.java │ ├── proxy # Mapper代理相关 │ │ ├── MapperProxy.java │ │ └── MapperProxyFactory.java │ └── datasource # 数据源相关 │ ├── DataSource.java │ └── SimpleDataSource.java ├── mapper # Mapper接口 │ └── UserMapper.java ├── pojo # 实体类 │ └── User.java ├── test # 测试类 │ └── HandwriteMyBatisTest.java └── resources # 配置文件 └── config ├── mybatis-config.xml └── UserMapper.xml

九、使用说明与注意事项

9.1 项目使用步骤

  1. 克隆/下载项目代码,导入IDE;

  2. 执行MySQL脚本创建数据库和表;

  3. 修改mybatis-config.xml中的数据库连接信息,适配本地环境;

  4. 执行mvn clean install构建项目;

  5. 运行HandwriteMyBatisTest类中的测试方法,验证功能;

  6. 扩展开发:可基于现有代码扩展动态SQL、连接池、缓存等功能。

9.2 注意事项

  1. 本文代码基于JDK 17编写,低于17的JDK版本可能存在语法兼容问题;

  2. 数据库版本为MySQL 8.0,使用低版本MySQL时,需修改驱动类名(如MySQL 5.x驱动类名为com.mysql.jdbc.Driver)和连接URL参数;

  3. 手写版本为简化实现,仅适用于学习和理解原理,不建议直接用于生产环境;

  4. 扩展功能时,需遵循MyBatis的核心设计思想,保持组件职责单一,确保代码可维护性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/14 5:34:03

揭秘FSNotes:现代笔记管理的智能解决方案实战指南

揭秘FSNotes&#xff1a;现代笔记管理的智能解决方案实战指南 【免费下载链接】fsnotes Notes manager for macOS/iOS 项目地址: https://gitcode.com/gh_mirrors/fs/fsnotes 你是否曾为寻找一款真正懂你的笔记应用而苦恼&#xff1f;在信息爆炸的时代&#xff0c;传统的…

作者头像 李华
网站建设 2026/2/16 11:38:03

Wan2.2-T2V-A14B在游戏开发中的应用:快速制作剧情动画

Wan2.2-T2V-A14B在游戏开发中的应用&#xff1a;快速制作剧情动画 你有没有经历过这样的场景&#xff1f;策划熬夜写完一段感人至深的主线剧情&#xff0c;兴冲冲地拿给美术&#xff1a;“咱们来做个过场动画吧&#xff01;” 结果对方淡淡回一句&#xff1a;“这个镜头至少要三…

作者头像 李华
网站建设 2026/2/12 15:23:25

Redmine项目管理平台终极使用指南:新手必读FAQ

Redmine是一个基于Ruby on Rails框架开发的灵活项目管理Web应用&#xff0c;为团队协作、任务跟踪和项目规划提供全面解决方案。本指南采用FAQ问答形式&#xff0c;帮你快速掌握这个强大的项目管理工具。 【免费下载链接】redmine Mirror of redmine code source - Official Su…

作者头像 李华
网站建设 2026/2/17 13:50:33

3大核心技能带你玩转大规模并行处理器编程

3大核心技能带你玩转大规模并行处理器编程 【免费下载链接】大规模并行处理器程序设计资源介绍 《大规模并行处理器程序设计》是一本深入浅出的并行程序设计书籍&#xff0c;全面系统地介绍了并行程序设计与GPU体系结构的基本概念。本书详细探讨了用于构建并行程序的各种技术&a…

作者头像 李华
网站建设 2026/2/11 19:48:49

轻松捕获网络视频:Video DownloadHelper 1.6.3版全方位使用指南

轻松捕获网络视频&#xff1a;Video DownloadHelper 1.6.3版全方位使用指南 【免费下载链接】VideoDownloadHelper插件下载 Video DownloadHelper 是一款广受欢迎的浏览器插件&#xff0c;专门用于从网页中下载视频和音频文件。它支持多种浏览器&#xff0c;并且能够识别并下载…

作者头像 李华
网站建设 2026/2/13 12:58:43

三相OW-PMSM无感电机仿真:基于零序反电动势的DQ轴数学模型与双逆变器调制策略的研究与实践

共直流母线型三相OW-PMSM无感-零序反电动势 -----------------仿真内容说明----------------- 1开绕组电机模型根据dq轴数学模型搭建 2位置信息从零序反电动势提取。 3电机首先经过I/f开环强拖至中高速&#xff0c;再切入速度闭环 4双逆变器调制策略基于120度解耦调制策略 5零序…

作者头像 李华