手机做照片下载网站南宁网站建设公司业绩

张小明 2026/3/12 7:23:34
手机做照片下载网站,南宁网站建设公司业绩,纯净水企业怎样做网站,互联网平台运营是做什么的一、引言在Java后端开发领域#xff0c;MyBatis作为一款轻量级ORM框架#xff0c;凭借其灵活的SQL控制、较低的学习成本和出色的性能#xff0c;成为了企业级开发中持久层的首选框架之一。大多数开发者都熟练使用MyBatis进行CRUD操作#xff0c;但对其底层实现逻辑却一知半…一、引言在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对象与数据库表的映射”具体拆解为以下需求配置解析加载mybatis-config.xml核心配置数据源、Mapper映射路径等和Mapper.xml映射配置SQL语句、参数映射、结果映射等Mapper代理通过动态代理机制让开发者直接调用Mapper接口方法即可执行对应SQL无需编写接口实现类SQL执行封装JDBC操作完成SQL参数绑定、语句执行结果映射将JDBC查询返回的ResultSet结果集自动映射为Java实体类对象会话管理提供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 version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.jam.demo/groupId artifactIdhandwrite-mybatis/artifactId version1.0-SNAPSHOT/version properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding lombok.version1.18.30/lombok.version spring.version6.1.5/spring.version fastjson2.version2.0.46/fastjson2.version guava.version33.2.1-jre/guava.version mysql.version8.4.0/mysql.version junit.version5.9.2/junit.version springdoc.version2.3.0/springdoc.version /properties dependencies !-- Lombok简化日志和实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version${lombok.version}/version scopeprovided/scope /dependency !-- Spring核心工具类 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency !-- FastJSON2JSON处理 -- dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version${fastjson2.version}/version /dependency !-- Guava集合工具类 -- dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version${guava.version}/version /dependency !-- MySQL驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version${mysql.version}/version scoperuntime/scope /dependency !-- JUnit5单元测试 -- dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter-api/artifactId version${junit.version}/version scopetest/scope /dependency dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter-engine/artifactId version${junit.version}/version scopetest/scope /dependency !-- Swagger3接口文档 -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version${springdoc.version}/version /dependency /dependencies build plugins !-- JDK编译插件 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.8.1/version configuration source17/source target17/target encodingUTF-8/encoding /configuration /plugin /plugins /build /project四、核心组件实现4.1 配置文件定义首先定义2个核心配置文件放在resources/config目录下4.1.1 mybatis-config.xml核心配置文件包含数据源信息和Mapper映射路径?xml version1.0 encodingUTF-8? configuration !-- 数据源配置 -- dataSource property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/handwrite_mybatis?useSSLfalseamp;serverTimezoneUTCamp;allowPublicKeyRetrievaltrue/ property nameusername valueroot/ property namepassword valueroot/ /dataSource !-- Mapper映射配置 -- mappers mapper resourceconfig/UserMapper.xml/ /mappers /configuration4.1.2 UserMapper.xmlMapper映射文件包含SQL语句、参数映射、结果映射?xml version1.0 encodingUTF-8? mapper namespacecom.jam.demo.mapper.UserMapper !-- 结果映射数据库字段与Java实体类属性映射 -- resultMap idUserResultMap typecom.jam.demo.pojo.User result columnid propertyid/ result columnusername propertyusername/ result columnage propertyage/ result columnemail propertyemail/ /resultMap !-- 根据ID查询用户 -- select idselectById parameterTypejava.lang.Long resultMapUserResultMap SELECT id, username, age, email FROM user WHERE id #{id} /select !-- 新增用户 -- insert idinsert parameterTypecom.jam.demo.pojo.User INSERT INTO user (username, age, email) VALUES (#{username}, #{age}, #{email}) /insert !-- 更新用户 -- update idupdate parameterTypecom.jam.demo.pojo.User UPDATE user SET username #{username}, age #{age}, email #{email} WHERE id #{id} /update !-- 删除用户 -- delete iddeleteById parameterTypejava.lang.Long DELETE FROM user WHERE id #{id} /delete /mapper4.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映射信息keynamespaceid如com.jam.demo.mapper.UserMapper.selectByIdvalueMapperStatement */ private MapString, 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并存入Configurationpackage 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); // 存入Configurationkeynamespaceid 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 ListT 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 ListT 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实体类 ListT 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 ListT 实体类列表 * throws SQLException SQL异常 */ SuppressWarnings(unchecked) private T ListT handleResultSet(ResultSet resultSet, MapperStatement mapperStatement) throws SQLException { ListT resultList new ArrayList(); String resultType mapperStatement.getResultType(); StringUtils.hasText(resultType, 查询SQL的resultType或resultMap不能为空); try { // 加载结果类型Class ClassT resultClass (ClassT) 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 MapperProxyT implements InvocationHandler { /** 核心配置 */ private Configuration configuration; /** Mapper接口类型 */ private ClassT mapperInterface; public MapperProxy(Configuration configuration, ClassT 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的keynamespacemethodName 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(未找到对应的MapperStatementkey: 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 MapperProxyFactoryT { /** Mapper接口类型 */ private ClassT mapperInterface; public MapperProxyFactory(ClassT 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(ClassT 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(ClassT type) { // 通过Mapper代理工厂创建代理对象 MapperProxyFactoryT 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创建SqlSessionpackage 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类会话工厂构建器通过配置解析器解析配置文件构建SqlSessionFactorypackage 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(jamexample.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_updateexample.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_updateexample.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版本17MySQL版本8.0数据库配置确保mybatis-config.xml中的数据库连接信息URL、用户名、密码与本地MySQL环境一致依赖构建执行mvn clean install构建项目下载所需依赖5.4.2 测试执行步骤执行MySQL脚本创建handwrite_mybatis数据库和user表在IDE中打开HandwriteMyBatisTest类执行testCrud()方法观察控制台日志和数据库数据变化验证CRUD功能是否正常。5.4.3 预期测试结果控制台日志输出“新增用户成功”“查询用户成功”“更新用户成功”“删除用户成功”等信息无异常抛出数据库中先新增一条用户数据更新后数据字段变化删除后数据不存在单元测试断言全部通过无失败用例。5.4.4 常见问题排查数据库连接失败检查MySQL服务是否启动mybatis-config.xml中的URL、用户名、密码是否正确配置文件找不到确保mybatis-config.xml和UserMapper.xml放在resources/config目录下Maven构建时能正确加载反射异常检查实体类属性名与数据库列名是否一致确保实体类有无参构造方法SQL执行异常检查Mapper.xml中的SQL语句语法是否正确参数占位符与方法参数是否匹配。六、核心原理深度剖析6.1 Mapper代理机制深度解析手写MyBatis的核心亮点之一是Mapper代理机制它避免了开发者编写繁琐的Mapper接口实现类。其底层基于JDK动态代理核心流程如下关键细节说明JDK动态代理要求被代理的类必须是接口这也是MyBatis的Mapper必须定义为接口的原因MapperProxy作为InvocationHandler负责拦截Mapper接口的所有方法调用过滤掉Object类的方法如toString()、hashCode()通过namespacemethodName构建唯一key从Configuration中获取对应的MapperStatement实现接口方法与SQL语句的绑定代理对象将方法调用转化为SQL执行最终将执行结果返回给调用方对调用方透明感觉直接调用接口方法就完成了数据库操作。6.2 配置解析原理配置解析模块的核心是将XML配置文件中的信息转化为Java对象Configuration、MapperStatement核心流程如下XmlConfigBuilder解析mybatis-config.xml先解析数据源配置创建SimpleDataSource存入Configuration再解析mappers节点加载对应的Mapper.xml文件交给XmlMapperBuilder解析XmlMapperBuilder解析Mapper.xml的namespace对应Mapper接口全类名和SQL标签select/insert/update/delete将每个SQL标签的信息封装为MapperStatement以namespaceid为key存入Configuration的mapperStatementMap中后续SQL执行时通过namespacemethodName即可快速获取对应的MapperStatement拿到SQL语句和参数/结果配置。6.3 SQL执行与结果映射原理6.3.1 SQL执行流程SQL执行的核心是Executor执行器它封装了JDBC的全套操作核心流程从Configuration中获取数据源通过数据源获取数据库连接处理原始SQL将#{}占位符替换为?生成可预处理的SQL语句创建PreparedStatement通过反射获取方法参数值绑定到?占位符上执行SQL查询执行executeQuery()增删改执行executeUpdate()关闭连接等资源简化实现实际MyBatis会用连接池管理连接。6.3.2 结果映射原理结果映射的核心是将ResultSet转化为Java实体类对象核心流程从MapperStatement中获取resultType结果类型全类名通过Class.forName()加载对应的实体类Class获取ResultSet的元数据ResultSetMetaData得到查询结果的列名和列数遍历ResultSet每一行数据对应一个实体类对象通过反射创建实体类实例遍历查询列通过列名获取实体类对应的属性调用Field.set()方法给属性赋值将所有实体类对象存入列表返回给调用方。6.4 与官方MyBatis的差异与扩展方向6.4.1 与官方MyBatis的核心差异本文实现的手写MyBatis是简化版与官方MyBatis的核心差异如下数据源手写版本使用简单的JDBC连接官方版本支持连接池如Druid、HikariCP、数据源工厂等SQL解析手写版本仅支持简单的#{}占位符替换官方版本支持复杂的动态SQLif/where/foreach等、OGNL表达式解析结果映射手写版本仅支持属性名与列名一致的映射官方版本支持下划线转驼峰、复杂结果映射一对一、一对多、resultMap高级配置等事务管理手写版本的事务提交/回滚是简化实现官方版本支持完整的事务管理器JDBC事务、MANAGED事务、事务隔离级别配置缓存机制手写版本未实现缓存官方版本支持一级缓存SqlSession级别、二级缓存Mapper级别插件机制手写版本未实现插件扩展官方版本支持插件机制可拦截Executor、StatementHandler等组件注解支持手写版本仅支持XML配置SQL官方版本支持Select、Insert等注解配置SQL。6.4.2 扩展方向进阶优化如果想进一步完善手写MyBatis可从以下方向扩展动态SQL支持实现if/where/foreach等动态SQL标签的解析增强SQL灵活性连接池集成集成HikariCP连接池优化连接管理提升性能高级结果映射支持下划线转驼峰、一对一/一对多关联查询映射缓存实现添加一级缓存和二级缓存减少数据库查询次数事务优化实现完整的事务管理器支持事务隔离级别和传播行为注解驱动支持通过注解配置SQL无需编写Mapper.xml插件机制提供插件扩展点支持自定义拦截器如日志增强、性能监控等。七、总结与面试考点梳理7.1 总结本文从0到1手写实现了一套简易但完整的MyBatis框架涵盖了MyBatis的核心组件配置解析、数据源、执行器、Mapper代理、会话管理和核心流程配置加载→会话创建→代理生成→SQL执行→结果映射。通过手写实现我们深入理解了MyBatis的底层原理配置解析本质是XML解析对象封装将配置信息存入核心配置容器Mapper代理的核心是JDK动态代理将接口方法调用转化为SQL执行SQL执行的核心是封装JDBC操作屏蔽底层细节结果映射的核心是反射机制实现ResultSet到Java对象的自动转化。掌握这些底层原理不仅能让我们更灵活地使用MyBatis进行开发还能快速定位和解决开发中遇到的框架相关问题。7.2 面试考点梳理手写MyBatis涉及的核心知识点也是面试中高频考察的考点整理如下MyBatis的核心组件有哪些各自的作用是什么答核心组件包括Configuration配置容器、SqlSessionFactory会话工厂、SqlSession会话、Executor执行器、MapperProxyMapper代理、MapperStatementMapper映射信息等。作用参考本文2.2节核心架构设计。MyBatis的Mapper代理机制原理是什么为什么Mapper接口不需要实现类答底层基于JDK动态代理通过MapperProxyFactory创建MapperProxy再通过Proxy.newProxyInstance生成代理对象。调用Mapper接口方法时会被MapperProxy的invoke()方法拦截转化为SQL执行因此不需要手动编写实现类。MyBatis的SQL执行流程是什么答加载配置文件→解析生成Configuration→创建SqlSessionFactory→获取SqlSession→获取Mapper代理对象→调用接口方法→代理对象拦截并获取MapperStatement→Executor执行SQL获取连接、绑定参数、执行SQL→结果映射→返回结果。MyBatis的结果映射原理是什么答通过反射机制加载结果类型Class获取ResultSet元数据列名、列数遍历ResultSet每一行数据创建实体类对象通过字段名反射赋值最终将实体类对象列表返回。MyBatis与JDBC的区别是什么答①MyBatis封装了JDBC的冗余代码如获取连接、预处理、关闭资源等②支持XML/注解配置SQL灵活易用③提供Mapper代理机制无需编写实现类④支持结果自动映射无需手动封装结果集⑤支持动态SQL、缓存等高级特性。什么是动态SQLMyBatis是如何实现动态SQL的答动态SQL是指根据参数条件动态拼接SQL语句。官方MyBatis通过XML标签if/where/foreach等和OGNL表达式解析在解析Mapper.xml时动态生成SQL语句。本文手写版本未实现可通过扩展XML解析逻辑实现。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 项目使用步骤克隆/下载项目代码导入IDE执行MySQL脚本创建数据库和表修改mybatis-config.xml中的数据库连接信息适配本地环境执行mvn clean install构建项目运行HandwriteMyBatisTest类中的测试方法验证功能扩展开发可基于现有代码扩展动态SQL、连接池、缓存等功能。9.2 注意事项本文代码基于JDK 17编写低于17的JDK版本可能存在语法兼容问题数据库版本为MySQL 8.0使用低版本MySQL时需修改驱动类名如MySQL 5.x驱动类名为com.mysql.jdbc.Driver和连接URL参数手写版本为简化实现仅适用于学习和理解原理不建议直接用于生产环境扩展功能时需遵循MyBatis的核心设计思想保持组件职责单一确保代码可维护性。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

德州市平原县建设局网站宣传片制作要求说明

亚马逊云工作空间与应用流服务指南 连接到你的工作空间 当收到电子邮件邀请后,用户可按以下步骤连接到他们的工作空间: 1. 在邀请邮件中,会有设置凭证的说明,按照说明设置你的凭证。 2. 凭证设置完成后,系统会提示你下载客户端。 3. 下载并安装工作空间客户端后,启动…

张小明 2026/3/5 5:41:08 网站建设

做设计在哪个网站接单建站有哪些公司

3步搞定!Layui表单设计器luminar零代码开发完全指南 【免费下载链接】luminar-layui-form-designer 基于layui的表单设计器,表单组件齐全,组件自定义交互完善,表单设计器已经基本实现了拖动布局,父子布局,项目实现了大…

张小明 2026/3/5 5:41:10 网站建设

遵义网站定制高职图书馆网站建设大赛

Hermes引擎实战开发手册:React Native性能优化与移动端启动加速 【免费下载链接】hermes A JavaScript engine optimized for running React Native. 项目地址: https://gitcode.com/gh_mirrors/hermes/hermes 前言 作为一名React Native开发者,…

张小明 2026/3/5 5:41:12 网站建设

建设部的网站hao123网址下载到桌面

在现代工业设计和工程领域,真实感的材质表现是决定设计品质的关键因素。SOLIDWORKS材质库大全作为一款专业的材质资源扩展包,为设计师和工程师提供了超过200种精心设计的材质类型,完美补充标准材质库的不足,让您的三维模型展现出前…

张小明 2026/3/5 5:41:09 网站建设

哪个网站做漫画可以有钱wordpress 网站显示加载时长

AgentFlow架构深度解析:7B模型如何实现智能体性能质的飞跃 【免费下载链接】agentflow-planner-7b 项目地址: https://ai.gitcode.com/hf_mirrors/AgentFlow/agentflow-planner-7b 智能体技术的核心痛点与破局思路 在当前的AI应用生态中,智能体…

张小明 2026/3/5 5:41:14 网站建设

石家庄网站定制模板建站视频门户网站建设服务器

近日,象征世界足球最高荣誉的大力神杯在青岛首次面向公众展出,引发了广泛关注。活动现场,海信冰箱携三款2026世界杯定制冰箱同步亮相,将“真空锁鲜”科技与体育竞技“保持最佳状态”的冠军精神深度融合,为消费者呈现一…

张小明 2026/3/5 5:41:14 网站建设