
1. 项目概述作为一名JavaWeb开发者日志技术、多表查询和分页查询是我们日常开发中必须掌握的核心技能。今天我要分享的是在实际项目中如何高效实现这些功能特别是如何通过动态SQL来优化分页查询性能。记得我刚入行时面对一个需要展示100万条数据的报表需求直接使用简单的limit分页导致页面加载需要30多秒。后来通过优化SQL和引入缓存最终将响应时间控制在1秒内。这段经历让我深刻认识到掌握这些技术不仅仅是会用API那么简单更需要理解背后的原理和优化思路。2. 日志技术实战2.1 日志框架选型在Java生态中我们常用的日志框架有Log4j2、Logback和SLF4J。我推荐使用Log4j2作为日志实现主要有以下几个原因异步日志性能优异比Logback快6-10倍支持丰富的Appender控制台、文件、数据库等灵活的Filter和Layout配置!-- Maven依赖配置 -- dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version2.17.1/version /dependency dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-slf4j-impl/artifactId version2.17.1/version /dependency2.2 日志最佳实践在实际项目中我总结了以下日志使用经验日志级别使用规范ERROR系统错误需要立即处理WARN潜在问题需要关注INFO重要业务流程日志DEBUG调试信息TRACE最详细的跟踪信息日志内容优化避免在日志中打印大对象使用占位符{}代替字符串拼接敏感信息脱敏处理// 错误示例 logger.info(用户信息 user); // 正确示例 logger.info(用户登录userId{}, userName{}, user.getId(), mask(user.getName()));提示生产环境一定要配置日志文件滚动策略和自动清理避免磁盘被日志文件占满。3. 多表查询实现3.1 多表关联方式在MySQL中我们常用的多表关联方式包括内连接(INNER JOIN)只返回两表中匹配的行左连接(LEFT JOIN)返回左表所有行右表不匹配则为NULL右连接(RIGHT JOIN)返回右表所有行左表不匹配则为NULL全连接(FULL JOIN)返回两表所有行不匹配则为NULL-- 示例查询部门及其员工信息 SELECT d.dept_name, e.emp_name, e.position FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id WHERE d.status 1 ORDER BY d.dept_id, e.emp_id3.2 MyBatis多表查询在MyBatis中处理多表查询我推荐以下几种方式使用ResultMap进行结果映射使用association和collection处理一对一、一对多关系对于复杂查询可以使用Select注解直接编写SQLresultMap iddeptWithEmployees typeDepartment id propertydeptId columndept_id/ result propertydeptName columndept_name/ collection propertyemployees ofTypeEmployee id propertyempId columnemp_id/ result propertyempName columnemp_name/ result propertyposition columnposition/ /collection /resultMap select idselectDeptWithEmployees resultMapdeptWithEmployees SELECT d.dept_id, d.dept_name, e.emp_id, e.emp_name, e.position FROM department d LEFT JOIN employee e ON d.dept_id e.dept_id WHERE d.status 1 /select4. 分页查询优化4.1 基础分页实现最简单的分页方式是使用LIMIT子句SELECT * FROM employee ORDER BY emp_id LIMIT 10 OFFSET 20; -- 每页10条第3页但在数据量大的情况下比如100万条记录这种分页方式会有性能问题因为MySQL需要先扫描前20条记录。4.2 优化方案键集分页对于大数据量表我推荐使用基于索引键的分页方式-- 第一页 SELECT * FROM employee ORDER BY emp_id LIMIT 10; -- 后续页假设上一页最后一条记录的emp_id是100 SELECT * FROM employee WHERE emp_id 100 ORDER BY emp_id LIMIT 10;这种方式的优点是不需要计算总页数每次查询都走索引性能稳定适合无限滚动加载场景4.3 MyBatis-Plus分页插件MyBatis-Plus提供了方便的分页插件// 配置类 Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } // 使用示例 PageEmployee page new Page(1, 10); // 当前页每页大小 PageEmployee result employeeMapper.selectPage(page, null); ListEmployee records result.getRecords(); long total result.getTotal();5. 动态SQL优化5.1 MyBatis动态SQLMyBatis提供了强大的动态SQL功能可以避免拼接SQL字符串select idselectEmployees resultTypeEmployee SELECT * FROM employee where if testdeptId ! null AND dept_id #{deptId} /if if testname ! null and name ! AND emp_name LIKE CONCAT(%, #{name}, %) /if if testposition ! null AND position #{position} /if /where ORDER BY emp_id /select5.2 分页查询优化实践结合动态SQL和分页查询我们可以实现高性能的复杂查询public PageResultEmployeeVO queryEmployeePage(EmployeeQuery query) { // 1. 构建分页参数 PageEmployee page new Page(query.getPageNum(), query.getPageSize()); // 2. 执行分页查询 LambdaQueryWrapperEmployee wrapper new LambdaQueryWrapper(); wrapper.eq(query.getDeptId() ! null, Employee::getDeptId, query.getDeptId()) .like(StringUtils.isNotBlank(query.getName()), Employee::getEmpName, query.getName()) .eq(StringUtils.isNotBlank(query.getPosition()), Employee::getPosition, query.getPosition()) .orderByAsc(Employee::getEmpId); PageEmployee employeePage employeeMapper.selectPage(page, wrapper); // 3. 转换为VO并返回 ListEmployeeVO voList convertToVOList(employeePage.getRecords()); return new PageResult(voList, employeePage.getTotal()); }5.3 连表分页查询优化对于需要连表的分页查询需要注意以下几点确保分页操作在主表上进行使用JOIN时注意表关联字段是否有索引考虑使用子查询先分页再关联-- 优化后的连表分页查询 SELECT e.*, d.dept_name FROM employee e JOIN department d ON e.dept_id d.dept_id WHERE e.emp_id IN ( SELECT emp_id FROM employee WHERE dept_id 1 ORDER BY emp_id LIMIT 10 OFFSET 20 )6. 常见问题与解决方案6.1 分页查询性能问题问题现象当数据量达到百万级时分页查询变慢特别是翻到后面几页时。解决方案使用基于索引键的分页如WHERE id last_id添加合适的索引考虑使用缓存前几页数据6.2 多表查询结果映射错误问题现象MyBatis返回的结果集中关联对象属性为null。排查步骤检查SQL语句是否正确关联了表检查ResultMap配置是否正确检查数据库字段和Java属性名是否匹配6.3 动态SQL条件失效问题现象动态SQL中的条件没有生效。常见原因参数值为null时if条件判断错误参数名与OGNL表达式不匹配使用了错误的比较运算符7. 性能优化建议根据我的项目经验以下优化措施可以显著提升查询性能索引优化为所有查询条件字段添加索引为排序字段添加索引考虑使用复合索引SQL优化避免SELECT *只查询需要的字段使用EXPLAIN分析SQL执行计划对于复杂查询考虑拆分为多个简单查询缓存策略使用Redis缓存热点数据实现二级缓存减少数据库压力考虑使用本地缓存提高响应速度连接池配置合理设置连接池大小监控连接池使用情况使用HikariCP等高性能连接池// HikariCP配置示例 Bean public DataSource dataSource() { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/my_db); config.setUsername(user); config.setPassword(password); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); return new HikariDataSource(config); }在实际项目中我发现很多性能问题都源于不合理的索引设计和过度复杂的SQL。建议在开发阶段就使用性能测试工具对关键查询进行压测及早发现并解决问题。