深圳外包公司网站,成都市住建局官网查询,apicloud怎么样,霞浦县建设局网站目录
#x1f3af; 先说说我遇到过的真实问题
✨ 摘要
1. MyBatis架构总览#xff1a;不只是写SQL的工具
1.1 别把MyBatis看简单了
1.2 核心组件职责解析
2. SqlSession#xff1a;MyBatis的大门
2.1 SqlSession的创建过程
2.2 执行器类型 先说说我遇到过的真实问题✨ 摘要1. MyBatis架构总览不只是写SQL的工具1.1 别把MyBatis看简单了1.2 核心组件职责解析2. SqlSessionMyBatis的大门2.1 SqlSession的创建过程2.2 执行器类型选对模式很重要3. Mapper接口绑定动态代理的魔法3.1 从接口到SQL到底发生了什么3.2 MapperMethod方法执行的指挥官4. 配置文件加载MyBatis的启动过程4.1 配置文件的完整加载流程4.2 ConfigurationMyBatis的大脑4.3 Mapper注册过程5. SQL执行过程从Java方法到数据库5.1 完整的SQL执行链路5.2 ExecutorSQL执行的指挥官5.3 StatementHandler真正的SQL执行者6. 缓存机制性能提升的关键6.1 一级缓存 vs 二级缓存6.2 一级缓存的坑6.3 二级缓存的正确使用7. 插件机制MyBatis的钩子7.1 插件原理责任链模式7.2 插件的执行顺序8. 类型处理器Java与数据库的桥梁8.1 内置类型处理器8.2 自定义类型处理器9. 实战性能优化与问题排查9.1 性能优化技巧技巧1合理使用批量操作技巧2使用分页查询技巧3合理使用缓存9.2 常见问题排查问题1N1查询问题问题2参数绑定错误问题3延迟加载问题10. 企业级最佳实践10.1 我的MyBatis军规 第一条明确SQL写在XML还是注解 第二条合理使用缓存 第三条批量操作要用批量模式 第四条监控SQL性能 第五条代码可读性10.2 生产环境配置模板11. 最后的话 推荐阅读官方文档源码学习实践指南性能优化 先说说我遇到过的真实问题去年我们团队接手一个老系统用的是MyBatis但没一个人懂原理。有天线上突然报错Too many connections数据库连接池爆了。我们查了三天最后发现是某个Mapper方法里写了复杂的动态SQL每次执行都创建新SqlSession但没人close。更绝的是有次性能优化我们发现某个查询方法被调用了1000次但实际只需要查一次数据库。后来一查是MyBatis一级缓存在作怪但团队里没人知道MyBatis还有缓存。这些事让我明白不懂MyBatis源码的程序员就像开自动挡车不知道变速箱原理早晚要出事。✨ 摘要MyBatis作为Java生态中最流行的ORM框架其核心在于SqlSession的动态管理和Mapper接口的绑定机制。本文深度解析MyBatis从配置文件加载、SqlSession创建、Mapper代理生成到SQL执行的完整链路。通过源码级分析揭示动态代理、反射、JDBC封装等底层技术如何协同工作。结合性能测试数据和实战案例提供MyBatis优化策略和故障排查指南。1. MyBatis架构总览不只是写SQL的工具1.1 别把MyBatis看简单了很多人觉得MyBatis就是写SQL的框架比Hibernate简单多了。这话对了一半MyBatis确实让SQL更直观但它的内部设计绝不简单。看看这个调用链路图1MyBatis完整执行链路看到没从你的Mapper方法调用到真正执行SQL中间隔了至少7层每一层都有它的职责。1.2 核心组件职责解析组件英文职责类比SqlSessionSQL会话对外API入口餐厅服务员Executor执行器SQL执行调度厨师长StatementHandler语句处理器操作Statement厨师ParameterHandler参数处理器设置参数配菜员ResultSetHandler结果集处理器处理结果摆盘员MapperProxyMapper代理接口方法代理点餐员2. SqlSessionMyBatis的大门2.1 SqlSession的创建过程很多人以为SqlSession就是个简单的数据库连接包装大错特错看看它的创建过程// 这是SqlSessionFactory创建SqlSession的核心方法 public class DefaultSqlSessionFactory implements SqlSessionFactory { Override public SqlSession openSession() { return openSessionFromDataSource( configuration.getDefaultExecutorType(), // 执行器类型 null, // 事务隔离级别 false // 是否自动提交 ); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx null; try { // 1. 获取环境配置 final Environment environment configuration.getEnvironment(); // 2. 创建事务工厂 final TransactionFactory transactionFactory getTransactionFactoryFromEnvironment(environment); // 3. 创建事务 tx transactionFactory.newTransaction( environment.getDataSource(), level, autoCommit ); // 4. 创建执行器关键 final Executor executor configuration.newExecutor(tx, execType); // 5. 创建SqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { // 关闭事务 closeTransaction(tx); throw ExceptionFactory.wrapException(Error opening session. , e); } } }代码清单1SqlSession创建源码MyBatis 3.5关键点SqlSession不是简单的Connection包装它包含配置信息Configuration执行器Executor事务控制2.2 执行器类型选对模式很重要MyBatis有三种执行器性能差异很大public enum ExecutorType { SIMPLE, // 简单执行器 REUSE, // 重用执行器 BATCH // 批量执行器 }性能测试数据插入1000条记录执行器类型耗时(ms)内存占用适用场景SIMPLE125045MB通用查询REUSE98042MB频繁相同SQLBATCH35050MB批量插入测试环境MySQL 8.01000条记录插入代码示例// 使用批量执行器 try (SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper mapper session.getMapper(UserMapper.class); for (int i 0; i 1000; i) { User user new User(user i, email i test.com); mapper.insert(user); } session.commit(); // 一次性提交 }3. Mapper接口绑定动态代理的魔法3.1 从接口到SQL到底发生了什么这是MyBatis最神奇的地方。你写个接口MyBatis就能执行SQLpublic interface UserMapper { Select(SELECT * FROM users WHERE id #{id}) User findById(Long id); } // 使用 User user userMapper.findById(1L); // 这里发生了什么答案动态代理 方法签名解析。看源码// MapperProxy是核心 public class MapperProxyT implements InvocationHandler, Serializable { private final SqlSession sqlSession; private final ClassT mapperInterface; private final MapMethod, MapperMethod methodCache; Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 1. 如果是Object的方法直接调用 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } // 2. 默认方法Java 8 if (method.isDefault()) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 3. 缓存中获取MapperMethod final MapperMethod mapperMethod cachedMapperMethod(method); // 4. 执行SQL return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k - new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()) ); } }代码清单2MapperProxy核心源码3.2 MapperMethod方法执行的指挥官MapperMethod是关键桥梁它把Java方法调用翻译成SQL执行public class MapperMethod { private final SqlCommand command; // SQL命令信息 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()) { // 有ResultHandler的情况 executeWithResultHandler(sqlSession, args); result null; } else if (method.returnsMany()) { // 返回集合 result executeForMany(sqlSession, args); } else if (method.returnsMap()) { // 返回Map 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; case FLUSH: result sqlSession.flushStatements(); break; default: throw new BindingException(Unknown execution method for: command.getName()); } return result; } // 处理返回集合的情况 private E Object executeForMany(SqlSession sqlSession, Object[] args) { ListE result; Object param method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds method.extractRowBounds(args); result sqlSession.selectList(command.getName(), param, rowBounds); } else { result sqlSession.selectList(command.getName(), param); } // 处理返回数组的情况 if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; } }代码清单3MapperMethod执行逻辑4. 配置文件加载MyBatis的启动过程4.1 配置文件的完整加载流程很多人以为MyBatis配置就是读个XML文件太天真了看看这流程图2MyBatis配置文件加载流程4.2 ConfigurationMyBatis的大脑Configuration对象是MyBatis的全局配置中心它缓存了所有信息public class Configuration { // 环境信息 protected Environment environment; // 注册的Mapper protected final MapperRegistry mapperRegistry new MapperRegistry(this); // 拦截器链 protected final InterceptorChain interceptorChain new InterceptorChain(); // 类型处理器注册表 protected final TypeHandlerRegistry typeHandlerRegistry new TypeHandlerRegistry(); // 类型别名注册表 protected final TypeAliasRegistry typeAliasRegistry new TypeAliasRegistry(); // 语言驱动注册表 protected final LanguageDriverRegistry languageDriverRegistry new LanguageDriverRegistry(); // 映射的SQL语句 protected final MapString, MappedStatement mappedStatements new StrictMap(Mapped Statements collection); // 缓存 protected final MapString, Cache caches new StrictMap(Caches collection); // 结果映射 protected final MapString, ResultMap resultMaps new StrictMap(Result Maps collection); // 参数映射 protected final MapString, ParameterMap parameterMaps new StrictMap(Parameter Maps collection); // 加载的关键方法 public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); } public T void addMapper(ClassT type) { mapperRegistry.addMapper(type); } public T T getMapper(ClassT type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); } public MappedStatement getMappedStatement(String id) { return getMappedStatement(id, true); } }代码清单4Configuration核心属性关键点Configuration是单例的全局唯一。这也是为什么MyBatis的插件Interceptor是全局生效的。4.3 Mapper注册过程Mapper接口是怎么注册到MyBatis中的看代码public class MapperRegistry { private final Configuration config; private final MapClass?, MapperProxyFactory? knownMappers new HashMap(); public T void addMapper(ClassT type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException(Type type is already known to the MapperRegistry.); } boolean loadCompleted false; try { // 1. 添加到knownMappers knownMappers.put(type, new MapperProxyFactory(type)); // 2. 解析Mapper注解 MapperAnnotationBuilder parser new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } public T T getMapper(ClassT type, SqlSession sqlSession) { // 获取Mapper代理工厂 final MapperProxyFactoryT mapperProxyFactory (MapperProxyFactoryT) knownMappers.get(type); if (mapperProxyFactory null) { throw new BindingException(Type type is not known to the MapperRegistry.); } try { // 创建代理实例 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException(Error getting mapper instance. Cause: e, e); } } }代码清单5Mapper注册过程5. SQL执行过程从Java方法到数据库5.1 完整的SQL执行链路这是MyBatis最复杂的部分看这张图图3SQL执行完整链路5.2 ExecutorSQL执行的指挥官Executor是执行器的核心它负责调度整个执行过程public abstract class BaseExecutor implements Executor { // 一级缓存本地缓存 protected PerpetualCache localCache; Override public E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 1. 获取BoundSql包含实际SQL和参数 BoundSql boundSql ms.getBoundSql(parameter); // 2. 创建缓存Key CacheKey key createCacheKey(ms, parameter, rowBounds, boundSql); // 3. 执行查询 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } SuppressWarnings(unchecked) Override public E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 检查一级缓存 ListE list (ListE) localCache.getObject(key); if (list ! null) { // 缓存命中 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 缓存未命中从数据库查询 list queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } return list; } private E ListE queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ListE list; // 先放入缓存占位 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 真正的查询 list doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 移除占位符 localCache.removeObject(key); } // 放入缓存 localCache.putObject(key, list); return list; } }代码清单6BaseExecutor查询逻辑5.3 StatementHandler真正的SQL执行者StatementHandler负责创建和执行Statementpublic class PreparedStatementHandler implements StatementHandler { Override public E ListE query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps (PreparedStatement) statement; // 执行查询 ps.execute(); // 处理结果集 return resultSetHandler.handleResultSets(ps); } Override public void parameterize(Statement statement) throws SQLException { // 设置参数 parameterHandler.setParameters((PreparedStatement) statement); } Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { // 准备Statement Statement statement null; try { // 创建PreparedStatement statement instantiateStatement(connection); // 设置超时 setStatementTimeout(statement, transactionTimeout); // 设置FetchSize setFetchSize(statement); return statement; } catch (SQLException e) { closeStatement(statement); throw e; } } protected Statement instantiateStatement(Connection connection) throws SQLException { String sql boundSql.getSql(); // 处理返回主键的情况 if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames mappedStatement.getKeyColumns(); if (keyColumnNames null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() ! null) { // 设置ResultSet类型 return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { // 普通的PreparedStatement return connection.prepareStatement(sql); } } }代码清单7StatementHandler执行SQL6. 缓存机制性能提升的关键6.1 一级缓存 vs 二级缓存很多人分不清MyBatis的两种缓存这很危险缓存类型作用范围生命周期默认开启注意点一级缓存SqlSession级别会话期间是同一个SqlSession有效二级缓存Mapper级别应用级别否需要手动开启6.2 一级缓存的坑我踩过最深的坑就是一级缓存。看这个例子// 错误示例 try (SqlSession session sqlSessionFactory.openSession()) { UserMapper mapper session.getMapper(UserMapper.class); // 第一次查询 User user1 mapper.findById(1L); // 查数据库 System.out.println(user1); // 第二次查询 User user2 mapper.findById(1L); // 从一级缓存获取 System.out.println(user2); // 和user1是同一个对象 // 修改user1 user1.setName(修改后); // 第三次查询 User user3 mapper.findById(1L); // 还是从缓存获取 System.out.println(user3.getName()); // 输出修改后数据不一致 }问题一级缓存导致数据不一致而且同一个对象被修改会影响缓存。解决方案// 方案1清除缓存 session.clearCache(); // 方案2创建新的SqlSession // 方案3关闭一级缓存不推荐6.3 二级缓存的正确使用二级缓存更复杂但用好了性能提升明显!-- 开启二级缓存 -- mapper namespacecom.example.UserMapper cache evictionLRU flushInterval60000 size512 readOnlytrue/ select idfindById resultTypeUser useCachetrue SELECT * FROM users WHERE id #{id} /select /mapper性能测试查询同一数据1000次缓存类型平均耗时(ms)数据库查询次数内存占用无缓存12501000低一级缓存451中二级缓存151高注意二级缓存是跨SqlSession的要确保缓存的实体类实现Serializable。7. 插件机制MyBatis的钩子7.1 插件原理责任链模式MyBatis插件基于责任链模式实现可以拦截4大对象ExecutorStatementHandlerParameterHandlerResultSetHandlerIntercepts({ Signature( type StatementHandler.class, method prepare, args {Connection.class, Integer.class} ) }) public class SlowSqlInterceptor implements Interceptor { private static final Logger log LoggerFactory.getLogger(SlowSqlInterceptor.class); private long slowSqlThreshold 1000; // 1秒 Override public Object intercept(Invocation invocation) throws Throwable { long startTime System.currentTimeMillis(); try { // 继续执行 return invocation.proceed(); } finally { long costTime System.currentTimeMillis() - startTime; if (costTime slowSqlThreshold) { StatementHandler handler (StatementHandler) invocation.getTarget(); BoundSql boundSql handler.getBoundSql(); log.warn(慢SQL检测: {}, 耗时: {}ms, 参数: {}, boundSql.getSql(), costTime, boundSql.getParameterObject()); // 可以发送告警 AlertManager.sendSlowSqlAlert(boundSql.getSql(), costTime); } } } Override public Object plugin(Object target) { // 创建代理 return Plugin.wrap(target, this); } Override public void setProperties(Properties properties) { // 读取配置 String threshold properties.getProperty(slowSqlThreshold); if (threshold ! null) { this.slowSqlThreshold Long.parseLong(threshold); } } }代码清单8慢SQL监控插件7.2 插件的执行顺序插件是按配置顺序执行的这很重要plugins !-- 先执行插件1 -- plugin interceptorcom.example.plugin1 property nameproperty1 valuevalue1/ /plugin !-- 再执行插件2 -- plugin interceptorcom.example.plugin2 property nameproperty2 valuevalue2/ /plugin /plugins执行顺序原始对象 ↓ 插件2代理 ↓ 插件1代理 ↓ 目标方法8. 类型处理器Java与数据库的桥梁8.1 内置类型处理器MyBatis内置了丰富的类型处理器但很多人不知道Java类型JDBC类型处理器说明StringVARCHARStringTypeHandler字符串IntegerINTEGERIntegerTypeHandler整数DateTIMESTAMPDateTypeHandler日期EnumVARCHAREnumTypeHandler枚举存名字EnumINTEGEREnumOrdinalTypeHandler枚举存序号booleanBOOLEANBooleanTypeHandler布尔8.2 自定义类型处理器我做过一个需求把JSON字符串存到数据库。用自定义类型处理器很方便// 1. 定义处理器 MappedTypes(MyData.class) MappedJdbcTypes(JdbcType.VARCHAR) public class JsonTypeHandler extends BaseTypeHandlerMyData { private final ObjectMapper objectMapper new ObjectMapper(); Override public void setNonNullParameter(PreparedStatement ps, int i, MyData parameter, JdbcType jdbcType) throws SQLException { try { String json objectMapper.writeValueAsString(parameter); ps.setString(i, json); } catch (JsonProcessingException e) { throw new SQLException(对象转JSON失败, e); } } Override public MyData getNullableResult(ResultSet rs, String columnName) throws SQLException { String json rs.getString(columnName); return parseJson(json); } Override public MyData getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String json rs.getString(columnIndex); return parseJson(json); } Override public MyData getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String json cs.getString(columnIndex); return parseJson(json); } private MyData parseJson(String json) { if (json null || json.isEmpty()) { return null; } try { return objectMapper.readValue(json, MyData.class); } catch (IOException e) { throw new RuntimeException(JSON解析失败: json, e); } } } // 2. 注册处理器 MappedTypes(MyData.class) MappedJdbcTypes(JdbcType.VARCHAR) public class JsonTypeHandler extends BaseTypeHandlerMyData { // 实现... } // 3. 在配置中注册 typeHandlers typeHandler handlercom.example.JsonTypeHandler/ /typeHandlers代码清单9自定义JSON类型处理器9. 实战性能优化与问题排查9.1 性能优化技巧技巧1合理使用批量操作// 错误每次insert都提交 for (User user : userList) { userMapper.insert(user); // 每次都有commit } // 正确批量提交 try (SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper mapper session.getMapper(UserMapper.class); for (User user : userList) { mapper.insert(user); } session.commit(); // 一次性提交 }性能对比插入1000条记录逐条提交1250ms批量提交350ms提升72%技巧2使用分页查询// 错误一次性查询所有 ListUser users userMapper.findAll(); // 如果数据量大内存爆炸 // 正确分页查询 PageHelper.startPage(1, 100); // 第1页每页100条 ListUser users userMapper.findByCondition(condition);技巧3合理使用缓存!-- 只缓存不经常变的配置数据 -- select idfindConfig resultTypeConfig useCachetrue flushCachefalse SELECT * FROM config WHERE type #{type} /select !-- 经常变的数据不要缓存 -- select idfindLatestOrders resultTypeOrder useCachefalse SELECT * FROM orders ORDER BY create_time DESC LIMIT 100 /select9.2 常见问题排查问题1N1查询问题现象查询列表然后循环查询详情产生大量SQL。解决方案!-- 使用关联查询 -- select idfindUserWithOrders resultMapuserWithOrders SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id o.user_id WHERE u.id #{id} /select resultMap iduserWithOrders typeUser id propertyid columnid/ result propertyname columnname/ collection propertyorders ofTypeOrder id propertyid columnorder_id/ result propertyamount columnamount/ /collection /resultMap问题2参数绑定错误现象#{}和${}用错。区别#{}预编译防SQL注入${}字符串替换不安全-- 安全 WHERE name #{name} -- 变成 WHERE name ? -- 不安全但有特殊用途 ORDER BY ${orderBy} -- 动态排序字段问题3延迟加载问题现象关闭Session后访问懒加载属性报错。解决方案!-- 开启后需要管理Session生命周期 -- settings setting namelazyLoadingEnabled valuetrue/ setting nameaggressiveLazyLoading valuefalse/ /settings或者使用FetchType.EAGERpublic class User { OneToMany(fetch FetchType.EAGER) // 立即加载 private ListOrder orders; }10. 企业级最佳实践10.1 我的MyBatis军规经过多年实践我总结了一套MyBatis最佳实践 第一条明确SQL写在XML还是注解简单SQL用注解复杂SQL、动态SQL用XML不要混用 第二条合理使用缓存配置类数据用二级缓存事务性数据不用缓存缓存要设置合理的超时时间 第三条批量操作要用批量模式插入/更新大量数据用ExecutorType.BATCH记得手动提交事务 第四条监控SQL性能用插件监控慢SQL定期分析执行计划设置合理的超时时间 第五条代码可读性SQL要格式化复杂的SQL要加注释参数要有明确的命名10.2 生产环境配置模板!-- mybatis-config.xml -- configuration !-- 设置 -- settings !-- 开启下划线转驼峰 -- setting namemapUnderscoreToCamelCase valuetrue/ !-- 开启二级缓存 -- setting namecacheEnabled valuetrue/ !-- 查询超时时间 -- setting namedefaultStatementTimeout value30/ !-- 返回null时调用setter方法 -- setting namecallSettersOnNulls valuetrue/ !-- 日志实现 -- setting namelogImpl valueSLF4J/ /settings !-- 类型别名 -- typeAliases package namecom.example.model/ /typeAliases !-- 插件 -- plugins !-- 分页插件 -- plugin interceptorcom.github.pagehelper.PageInterceptor property namereasonable valuetrue/ /plugin !-- 慢SQL监控 -- plugin interceptorcom.example.SlowSqlInterceptor property nameslowSqlThreshold value1000/ /plugin /plugins !-- 环境 -- environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ !-- 连接池配置 -- property namepoolMaximumActiveConnections value20/ property namepoolMaximumIdleConnections value10/ property namepoolMaximumCheckoutTime value20000/ /dataSource /environment /environments !-- 映射文件 -- mappers package namecom.example.mapper/ /mappers /configuration11. 最后的话MyBatis就像一把瑞士军刀功能多但要用对地方。用好了它能让你如虎添翼用不好它就是定时炸弹。我见过太多团队在MyBatis上栽跟头有的因为缓存问题导致数据不一致有的因为SQL注入导致安全漏洞有的因为N1查询拖垮数据库。记住框架是工具不是黑盒子。理解原理掌握细节才能在关键时刻解决问题。 推荐阅读官方文档MyBatis官方文档 - 最权威的参考MyBatis-Spring整合文档 - Spring整合指南源码学习MyBatis GitHub仓库 - 直接看源码MyBatis Spring Boot Starter - Spring Boot整合实践指南阿里巴巴Java开发手册 - MyBatis章节必看MyBatis最佳实践 - 官方博客性能优化MySQL性能优化 - 数据库层面优化Arthas诊断工具 - MyBatis性能诊断最后建议打开IDE创建一个简单的MyBatis项目然后打断点跟一遍源码。从SqlSessionFactoryBuilder.build()开始一步步看MyBatis是怎么启动的Mapper是怎么代理的SQL是怎么执行的。亲手跟一遍胜过看十篇文章。