1. MyBatis动态SQL深度解析
1.1 为什么需要动态SQL
在实际业务开发中,我们经常遇到需要根据不同条件拼接SQL语句的场景。比如电商平台的商品搜索功能,用户可能根据商品名称、价格区间、分类等多个条件进行组合查询。如果为每种可能的组合都编写单独的SQL语句,会导致代码臃肿且难以维护。
MyBatis提供的动态SQL功能完美解决了这个问题。它允许我们在XML映射文件中,使用特定的标签来构建灵活的SQL语句。这种声明式的写法既保持了SQL的可读性,又具备了Java代码的灵活性。
1.2 核心动态SQL标签详解
1.2.1 标签的实战技巧
<select id="searchProducts" resultType="Product"> SELECT * FROM products WHERE 1=1 <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price <= #{maxPrice} </if> </select>注意:WHERE 1=1是为了避免所有条件都不满足时SQL语法错误。但在MyBatis 3.4.2+版本中,更推荐使用 标签替代。
1.2.2 标签的巧妙应用
当需要实现类似Java中switch-case的逻辑时, 标签是最佳选择:
<select id="getUserList" resultType="User"> SELECT * FROM users <where> <choose> <when test="role == 'admin'"> AND is_admin = 1 </when> <when test="role == 'vip'"> AND is_vip = 1 </when> <otherwise> AND status = 'active' </otherwise> </choose> </where> </select>1.2.3 标签的高效批处理
批量操作是数据库性能优化的关键点之一。 标签让批量操作变得简单:
<insert id="batchInsertUsers"> INSERT INTO users (name, email) VALUES <foreach collection="list" item="user" separator=","> (#{user.name}, #{user.email}) </foreach> </insert>重要提示:MySQL默认接受的最大SQL长度由max_allowed_packet参数控制(默认4MB),批量操作时需注意数据量。
1.2.4标签的更新优化
在更新操作中,
<update id="updateUser"> UPDATE users <set> <if test="name != null">name = #{name},</if> <if test="email != null">email = #{email},</if> <if test="password != null">password = #{password},</if> </set> WHERE id = #{id} </update>1.3 动态SQL性能优化实践
动态SQL虽然方便,但不当使用可能导致性能问题。以下是我总结的优化经验:
- 避免过度动态化:对于高频查询,建议拆分为多个明确的方法,而非一个超级动态查询
- 索引友好设计:确保动态条件都能利用到索引,特别是范围查询
- 参数预处理:对于LIKE查询,建议在Java代码中预处理好%通配符
- 批量操作分片:当数据量过大时,应将批量操作分多次执行
2. MyBatis Generator插件深度定制
2.1 MBG核心工作原理
MyBatis Generator(MBG)通过解析数据库表结构,自动生成:
- 实体类(POJO)
- Mapper接口
- XML映射文件
其核心流程为:
- 读取配置文件(generatorConfig.xml)
- 连接数据库获取元数据
- 根据模板生成代码
- 可选执行插件扩展点
2.2 标准配置详解
一个完整的generatorConfig.xml示例:
<?xml version="1.0" encoding="UTF-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 id="mysql" targetRuntime="MyBatis3"> <!-- 数据库连接配置 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis_demo" userId="root" password="123456"> </jdbcConnection> <!-- 生成模型配置 --> <javaModelGenerator targetPackage="com.example.model" targetProject="src/main/java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <!-- 生成SQL映射文件配置 --> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!-- 生成Mapper接口配置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java"/> <!-- 表配置 --> <table tableName="%" > <generatedKey column="id" sqlStatement="MySql" identity="true"/> </table> </context> </generatorConfiguration>2.3 自定义插件开发实战
MBG提供了丰富的插件扩展点,我们可以通过实现PluginAdapter接口来定制生成逻辑:
public class CustomCommentPlugin extends PluginAdapter { @Override public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, ModelClassType modelClassType, ModelClass modelClass) { // 为字段添加Swagger注解 field.addAnnotation("@ApiModelProperty(value = \"" + introspectedColumn.getRemarks() + "\")"); return true; } @Override public boolean validate(List<String> warnings) { return true; } }在配置文件中启用插件:
<context id="mysql" targetRuntime="MyBatis3"> <plugin type="com.example.CustomCommentPlugin"/> <!-- 其他配置 --> </context>2.4 常用插件推荐
- Lombok插件:自动添加Lombok注解,减少样板代码
- Swagger插件:集成Swagger文档注解
- Serializable插件:让实体类实现Serializable接口
- Example增强插件:增强生成的Example类功能
3. 高级应用与性能优化
3.1 动态SQL与二级缓存整合
MyBatis的二级缓存可以显著提升查询性能,但与动态SQL结合时需要特别注意:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <select id="searchProducts" resultType="Product" useCache="true"> SELECT * FROM products <where> <!-- 动态条件 --> </where> </select>警告:当查询条件高度动态化时,可能导致缓存命中率极低,此时应禁用缓存。
3.2 分页查询优化方案
结合PageHelper实现高效分页:
// 在Service层 public PageInfo<Product> searchProducts(ProductQuery query, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<Product> list = productMapper.searchProducts(query); return new PageInfo<>(list); }对应的Mapper接口:
List<Product> searchProducts(@Param("query") ProductQuery query);3.3 复杂嵌套查询处理
对于复杂关联查询,推荐使用 的嵌套映射:
<resultMap id="OrderDetailMap" type="Order"> <id property="id" column="order_id"/> <!-- 其他字段 --> <collection property="items" ofType="OrderItem"> <id property="id" column="item_id"/> <!-- 其他字段 --> <association property="product" javaType="Product"> <id property="id" column="product_id"/> <!-- 其他字段 --> </association> </collection> </resultMap> <select id="getOrderWithDetails" resultMap="OrderDetailMap"> SELECT o.*, i.*, p.* FROM orders o LEFT JOIN order_items i ON o.id = i.order_id LEFT JOIN products p ON i.product_id = p.id WHERE o.id = #{id} </select>4. 生产环境最佳实践
4.1 代码生成规范
版本控制策略:
- 生成的实体类应该纳入版本控制
- XML映射文件建议不纳入版本控制(可通过MBG重新生成)
- 自定义的SQL片段应放在单独的XML文件中
目录结构建议:
src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── model/ # 实体类 │ │ ├── mapper/ # Mapper接口 │ │ └── query/ # 查询条件封装类 │ └── resources/ │ ├── mapper/ # 自动生成的XML │ └── mybatis-mapper/ # 自定义的XML4.2 常见问题排查
SQL注入防护:
- 始终使用#{}而非${}(除非必要)
- 动态表名/列名场景使用${}时,必须做白名单校验
N+1查询问题:
- 使用 的fetchType="eager"或全局aggressiveLazyLoading=false
- 复杂关联查询推荐使用JOIN一次性获取
类型处理器问题:
- 枚举类型推荐实现BaseTypeHandler或使用MyBatis提供的EnumTypeHandler
- 自定义类型处理器需在配置中注册
4.3 性能监控建议
- 开启MyBatis的SQL日志:
# application.properties logging.level.org.mybatis=debug- 使用P6Spy进行SQL分析:
# 数据源配置 spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/db spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver- 关键指标监控:
- 平均查询时间
- 慢查询数量
- 缓存命中率
5. 扩展与集成方案
5.1 Spring Boot深度集成
在Spring Boot中优化MyBatis配置:
@Configuration @MapperScan("com.example.mapper") public class MyBatisConfig { @Bean public ConfigurationCustomizer mybatisConfigurationCustomizer() { return configuration -> { // 开启驼峰命名转换 configuration.setMapUnderscoreToCamelCase(true); // 设置默认执行器类型 configuration.setDefaultExecutorType(ExecutorType.REUSE); }; } @Bean public SqlSessionFactoryBean sqlSessionFactoryBean( DataSource dataSource) throws IOException { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); // 配置XML文件位置 factory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:mybatis-mapper/*.xml")); return factory; } }5.2 多数据源支持方案
@Configuration @MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory") public class PrimaryDataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean public SqlSessionFactory primarySqlSessionFactory( @Qualifier("primaryDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:mapper/primary/*.xml")); return factory.getObject(); } @Bean public DataSourceTransactionManager primaryTransactionManager( @Qualifier("primaryDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }5.3 与Spring Data JPA混合使用
在既有JPA项目中引入MyBatis:
@Repository public interface UserJpaRepository extends JpaRepository<User, Long> { // JPA查询方法 } @Mapper public interface UserMyBatisMapper { // MyBatis查询方法 @Select("SELECT * FROM users WHERE name LIKE CONCAT('%', #{name}, '%')") List<User> findByNameLike(String name); } @Service @RequiredArgsConstructor public class UserService { private final UserJpaRepository userJpaRepository; private final UserMyBatisMapper userMyBatisMapper; // 业务方法可以混合使用两种技术 }6. 前沿技术与未来演进
6.1 MyBatis-Plus对比分析
MyBatis-Plus在MyBatis基础上提供了更多开箱即用的功能:
| 特性 | MyBatis | MyBatis-Plus |
|---|---|---|
| CRUD接口 | 需手动编写 | 内置通用Mapper |
| 条件构造器 | 需手写SQL | Lambda表达式方式 |
| 分页插件 | 需集成PageHelper | 内置分页插件 |
| 代码生成器 | 需配置MBG | 内置更强大的生成器 |
| 乐观锁支持 | 需手动实现 | 内置@Version支持 |
6.2 Kotlin支持实践
MyBatis官方已提供Kotlin支持,可以编写更简洁的Mapper接口:
@Mapper interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") fun findById(id: Long): User? @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") fun insert(user: User): Int }6.3 云原生适配方案
在Kubernetes环境中优化MyBatis配置:
- 使用ConfigMap管理MyBatis配置:
apiVersion: v1 kind: ConfigMap metadata: name: mybatis-config data: mybatis-config.xml: | <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="cacheEnabled" value="true"/> </settings> </configuration>- 动态数据源配置:
@Value("${database.url}") private String dbUrl; @Bean public DataSource dataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(dbUrl); // 其他配置 return new HikariDataSource(config); }在实际项目开发中,我发现动态SQL的最佳实践是保持简洁性。过度复杂的动态SQL虽然功能强大,但会显著降低代码可读性和维护性。建议将特别复杂的查询逻辑拆分为多个方法,或者在应用层进行组合。对于MyBatis Generator,定制适合团队规范的插件可以大幅提升开发效率,但要注意避免生成过多冗余代码。