MyBatis动态SQL与逆向工程实战指南

发布时间:2026/8/9 8:10:39
MyBatis动态SQL与逆向工程实战指南 1. MyBatis动态SQL与逆向工程实战解析作为Java开发者我们经常需要与数据库打交道。MyBatis作为一款优秀的持久层框架其动态SQL和逆向工程功能能极大提升开发效率。今天我就结合自己多年使用经验详细讲解这两个核心功能的原理和实战技巧。1.1 为什么需要动态SQL在传统JDBC开发中我们经常需要根据不同的条件拼接SQL语句这不仅容易出错而且代码可读性差。MyBatis的动态SQL功能完美解决了这个问题它允许我们在XML映射文件中使用条件标签来构建灵活的SQL语句。举个例子当我们需要根据用户输入的不同查询条件来检索数据时动态SQL可以让我们避免编写大量重复的if-else逻辑。这不仅减少了代码量还提高了可维护性。1.2 逆向工程的价值逆向工程(Reverse Engineering)是MyBatis提供的一个强大工具它可以根据数据库表结构自动生成对应的实体类、Mapper接口和XML映射文件。这个功能特别适合在项目初期快速搭建持久层框架或者在对已有数据库进行二次开发时使用。我曾经参与过一个老系统重构项目数据库中有上百张表如果手动编写所有实体类和Mapper至少要花费两周时间。而使用MyBatis逆向工程配合一些定制化配置我们仅用半天就完成了基础代码的生成效率提升了数十倍。2. 动态SQL深度解析2.1 核心标签详解MyBatis提供了多种动态SQL标签每个都有其特定的使用场景if标签最基本的条件判断select idfindActiveBlogWithTitleLike resultTypeBlog SELECT * FROM BLOG WHERE state ACTIVE if testtitle ! null AND title like #{title} /if /selectchoose/when/otherwise实现多条件选择select idfindActiveBlogLike resultTypeBlog SELECT * FROM BLOG WHERE state ACTIVE choose when testtitle ! null AND title like #{title} /when when testauthor ! null and author.name ! null AND author_name like #{author.name} /when otherwise AND featured 1 /otherwise /choose /selecttrim/where/set解决SQL语法问题update idupdateAuthorIfNecessary update Author set if testusername ! nullusername#{username},/if if testpassword ! nullpassword#{password},/if if testemail ! nullemail#{email},/if /set where id#{id} /updateforeach处理集合迭代select idselectPostIn resultTypedomain.blog.Post SELECT * FROM POST P WHERE ID in foreach itemitem indexindex collectionlist open( separator, close) #{item} /foreach /select2.2 动态SQL性能优化虽然动态SQL很强大但使用不当也会带来性能问题。以下是我总结的几个优化建议避免过度使用动态SQL不是所有场景都需要动态SQL简单的CRUD操作使用静态SQL效率更高。合理使用缓存对于频繁执行且结果变化不大的动态SQL查询考虑配置二级缓存。注意SQL注入风险虽然MyBatis使用预编译语句但动态拼接SQL时仍需注意特殊字符处理。批量操作优化对于大批量数据操作使用foreach标签时注意设置合理的batchSize。提示在开发环境中可以使用MyBatis Log Free插件查看最终执行的SQL这对调试动态SQL非常有帮助。3. MyBatis逆向工程实战3.1 逆向工程配置详解MyBatis Generator(MBG)是官方提供的逆向工程工具。要使用它首先需要在项目中添加依赖dependency groupIdorg.mybatis.generator/groupId artifactIdmybatis-generator-core/artifactId version1.4.0/version /dependency然后创建generatorConfig.xml配置文件?xml version1.0 encodingUTF-8? !DOCTYPE generatorConfiguration PUBLIC -//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd generatorConfiguration context idmysqlTables targetRuntimeMyBatis3 jdbcConnection driverClasscom.mysql.jdbc.Driver connectionURLjdbc:mysql://localhost:3306/test userIdroot password123456 /jdbcConnection javaModelGenerator targetPackagecom.example.model targetProjectsrc/main/java property nameenableSubPackages valuetrue/ property nametrimStrings valuetrue/ /javaModelGenerator sqlMapGenerator targetPackagemapper targetProjectsrc/main/resources property nameenableSubPackages valuetrue/ /sqlMapGenerator javaClientGenerator typeXMLMAPPER targetPackagecom.example.mapper targetProjectsrc/main/java property nameenableSubPackages valuetrue/ /javaClientGenerator table tableName% generatedKey columnid sqlStatementMysql identitytrue/ /table /context /generatorConfiguration3.2 自定义逆向工程策略MBG提供了丰富的配置选项可以自定义生成策略忽略字段不生成特定的表字段table tableNameuser ignoreColumn columndeleted/ /table自定义类型转换处理特殊数据类型columnOverride columncreate_time javaTypejava.time.LocalDateTime/生成Example类用于构建复杂查询条件table tableNameuser property nameuseActualColumnNames valuetrue/ property nameenableCountByExample valuefalse/ property nameenableUpdateByExample valuefalse/ property nameenableDeleteByExample valuefalse/ property nameenableSelectByExample valuetrue/ property nameselectByExampleQueryId valuefalse/ /table自定义注释添加有意义的代码注释commentGenerator property namesuppressAllComments valuefalse/ property namesuppressDate valuetrue/ property nameaddRemarkComments valuetrue/ /commentGenerator3.3 执行逆向工程配置完成后可以通过以下方式执行逆向工程使用Maven插件plugin groupIdorg.mybatis.generator/groupId artifactIdmybatis-generator-maven-plugin/artifactId version1.4.0/version configuration configurationFilesrc/main/resources/generatorConfig.xml/configurationFile overwritetrue/overwrite /configuration dependencies dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.23/version /dependency /dependencies /plugin执行命令mvn mybatis-generator:generateJava代码方式public class MyBatisGenerator { public static void main(String[] args) throws Exception { ListString warnings new ArrayList(); boolean overwrite true; File configFile new File(generatorConfig.xml); ConfigurationParser cp new ConfigurationParser(warnings); Configuration config cp.parseConfiguration(configFile); DefaultShellCallback callback new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null); } }4. 高级技巧与常见问题4.1 动态SQL与PageHelper整合在实际项目中我们经常需要将动态SQL与分页插件结合使用。以PageHelper为例// 在Service层 public PageInfoUser searchUsers(UserQuery query, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListUser users userMapper.selectByQuery(query); return new PageInfo(users); } // 在Mapper.xml中 select idselectByQuery resultMapBaseResultMap select * from user where if testusername ! null and username ! and username like concat(%, #{username}, %) /if if teststatus ! null and status #{status} /if /where order by create_time desc /select4.2 枚举类型处理MyBatis提供了对枚举类型的良好支持。我们可以通过TypeHandler来处理枚举public enum UserStatus { ACTIVE(1, 活跃), INACTIVE(0, 禁用); private final int code; private final String desc; // 构造方法、getter省略 } public class UserStatusTypeHandler extends BaseTypeHandlerUserStatus { Override public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); } // 其他方法实现省略 }在配置文件中注册TypeHandlertypeHandlers typeHandler handlercom.example.handler.UserStatusTypeHandler javaTypecom.example.enums.UserStatus/ /typeHandlers4.3 常见问题解决方案SQL注入问题问题动态SQL中直接拼接用户输入可能导致SQL注入解决方案始终使用#{}占位符避免使用${}特殊字符转义问题XML中特殊字符(, , )需要转义解决方案if testage ! null AND age ![CDATA[ ]] #{age} /if批量插入性能差问题使用foreach批量插入时效率低解决方案设置rewriteBatchedStatementstruejdbc.urljdbc:mysql://localhost:3306/test?rewriteBatchedStatementstrue主键回写问题问题插入后无法获取自增主键解决方案配置useGeneratedKeysinsert idinsert useGeneratedKeystrue keyPropertyid insert into user (name) values (#{name}) /insert列名与属性名不一致问题数据库使用下划线命名Java使用驼峰命名解决方案配置mapUnderscoreToCamelCasemybatis.configuration.map-underscore-to-camel-casetrue4.4 性能监控与优化为了确保动态SQL的性能我们需要进行监控和优化启用慢SQL日志# 设置慢SQL阈值(毫秒) mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl mybatis.configuration.default-statement-timeout3000使用MyBatis-Plus性能分析插件Bean public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor interceptor new PerformanceInterceptor(); interceptor.setMaxTime(1000); // SQL执行最大时长超过自动停止运行 interceptor.setFormat(true); // 是否格式化代码 return interceptor; }SQL优化建议避免在循环中执行SQL合理使用索引减少不必要的列查询考虑使用延迟加载5. 实际项目中的应用5.1 复杂查询场景在实际项目中我们经常遇到复杂的多条件查询需求。下面是一个电商系统中商品查询的示例public class ProductQuery { private String name; private BigDecimal minPrice; private BigDecimal maxPrice; private Integer categoryId; private ListInteger statusList; private Date startDate; private Date endDate; // getter/setter省略 } // Mapper.xml中的动态SQL select idsearchProducts resultMapProductResultMap SELECT * FROM product where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testminPrice ! null AND price #{minPrice} /if if testmaxPrice ! null AND price #{maxPrice} /if if testcategoryId ! null AND category_id #{categoryId} /if if teststatusList ! null and statusList.size() 0 AND status IN foreach collectionstatusList itemstatus open( separator, close) #{status} /foreach /if if teststartDate ! null and endDate ! null AND create_time BETWEEN #{startDate} AND #{endDate} /if /where ORDER BY choose when testsortField priceprice/when when testsortField salessales/when otherwisecreate_time/otherwise /choose choose when testsortOrder descDESC/when otherwiseASC/otherwise /choose /select5.2 动态表名处理在某些分表场景下我们需要动态决定表名。虽然MyBatis官方不建议这样做但在某些特殊场景下是必要的// 使用Param注解指定表名 ListUser selectFromTable(Param(tableName) String tableName, Param(user) User user); // Mapper.xml select idselectFromTable resultTypeUser SELECT * FROM ${tableName} where if testuser.name ! null AND name #{user.name} /if if testuser.age ! null AND age #{user.age} /if /where /select注意使用动态表名时务必做好输入验证防止SQL注入。最好使用白名单机制限制可选的表名。5.3 与Spring Boot集成在Spring Boot项目中集成MyBatis非常简便添加依赖dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.0/version /dependency配置application.ymlmybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.model configuration: map-underscore-to-camel-case: true default-fetch-size: 100 default-statement-timeout: 30添加Mapper扫描MapperScan(com.example.mapper) SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }5.4 自定义插件开发MyBatis允许开发自定义插件来扩展功能。下面是一个简单的SQL执行时间统计插件Intercepts({ Signature(type StatementHandler.class, method query, args {Statement.class, ResultHandler.class}), Signature(type StatementHandler.class, method update, args {Statement.class}), Signature(type StatementHandler.class, method batch, args {Statement.class}) }) public class SqlCostTimeInterceptor implements Interceptor { private static final Logger logger LoggerFactory.getLogger(SqlCostTimeInterceptor.class); Override public Object intercept(Invocation invocation) throws Throwable { long startTime System.currentTimeMillis(); try { return invocation.proceed(); } finally { long endTime System.currentTimeMillis(); long costTime endTime - startTime; StatementHandler statementHandler (StatementHandler) invocation.getTarget(); String sql statementHandler.getBoundSql().getSql(); logger.info(SQL执行耗时: {}ms - {}, costTime, sql); } } Override public Object plugin(Object target) { return Plugin.wrap(target, this); } Override public void setProperties(Properties properties) { // 可以读取配置参数 } }注册插件Bean public SqlCostTimeInterceptor sqlCostTimeInterceptor() { return new SqlCostTimeInterceptor(); }6. 最佳实践与经验分享6.1 项目结构建议一个良好的MyBatis项目结构能提高代码的可维护性src/main/java ├── com.example │ ├── config # MyBatis配置类 │ ├── model # 实体类 │ ├── mapper # Mapper接口 │ ├── service # 业务服务 │ └── handler # TypeHandler src/main/resources ├── mapper # XML映射文件 ├── generator # 逆向工程配置 └── application.yml # 配置文件6.2 代码生成策略增量生成对于已有代码的表配置property nameoverwrite valuefalse/避免覆盖手动修改的代码。自定义模板可以通过修改Velocity模板来定制生成的代码风格。注释生成配置commentGenerator添加有意义的注释方便后续维护。6.3 性能调优经验连接池配置使用高性能连接池如HikariCPspring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000二级缓存对于读多写少的数据考虑启用二级缓存cache evictionLRU flushInterval60000 size1024 readOnlytrue/批量操作使用SqlSession的批量模式提高性能try (SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper mapper session.getMapper(UserMapper.class); for (int i 0; i 1000; i) { mapper.insert(new User(user i)); if (i % 200 0) { session.flushStatements(); } } session.commit(); }6.4 常见陷阱与规避N1查询问题问题在循环中查询关联数据导致性能问题解决方案使用collection或association实现一次性加载事务未提交问题忘记调用sqlSession.commit()导致数据未持久化解决方案使用Transactional注解或确保手动提交缓存一致性问题问题缓存数据与数据库不一致解决方案合理设置缓存过期策略或在数据修改时清除缓存大结果集内存溢出问题查询返回大量数据导致内存不足解决方案使用分页查询或游标方式处理大数据集6.5 监控与诊断启用MyBatis日志logging: level: org.mybatis: DEBUG使用Druid监控Bean public ServletRegistrationBeanStatViewServlet druidStatViewServlet() { ServletRegistrationBeanStatViewServlet registrationBean new ServletRegistrationBean(new StatViewServlet(), /druid/*); registrationBean.addInitParameter(loginUsername, admin); registrationBean.addInitParameter(loginPassword, admin); return registrationBean; }慢SQL监控Bean public FilterRegistrationBeanWebStatFilter druidWebStatFilter() { FilterRegistrationBeanWebStatFilter registrationBean new FilterRegistrationBean(new WebStatFilter()); registrationBean.addUrlPatterns(/*); registrationBean.addInitParameter(exclusions, *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*); return registrationBean; }7. 未来发展与替代方案7.1 MyBatis-Plus扩展MyBatis-Plus是MyBatis的增强工具提供了更多便捷功能通用Mapper减少基础CRUD代码public interface UserMapper extends BaseMapperUser { // 无需编写基础CRUD方法 }Lambda查询类型安全的查询条件ListUser users userMapper.selectList( Wrappers.UserlambdaQuery() .eq(User::getName, 张三) .gt(User::getAge, 18) .orderByAsc(User::getCreateTime) );自动填充自动处理创建时间、更新时间等字段TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;7.2 其他ORM框架比较虽然MyBatis很强大但了解其他ORM框架也很重要JPA/Hibernate优点标准规范、自动DDL、丰富的关联关系支持缺点复杂查询性能较差、学习曲线陡峭JOOQ优点类型安全的SQL、丰富的SQL功能支持缺点需要生成代码、社区相对较小Spring Data JDBC优点简单轻量、与Spring生态集成好缺点功能相对有限7.3 云原生趋势下的MyBatis随着云原生的发展MyBatis也在不断进化响应式支持MyBatis正在试验响应式编程支持Kubernetes集成动态数据源配置适应云环境Serverless适配优化冷启动性能在实际项目中我通常会根据团队技术栈和项目需求选择合适的持久层方案。对于需要精细控制SQL、处理复杂查询的场景MyBatis仍然是首选而对于简单的CRUD操作或快速原型开发JPA或MyBatis-Plus可能更合适。