在 Spring Boot + MyBatis 框架中,实现自定义类型处理器,如何使用呢?


在 Spring Boot + MyBatis 框架中,实现自定义类型处理器的生效范围控制可以通过以下方式实现:

自定义类型处理器

创建专用类型处理器,重写 BigDecimal 的转换逻辑:

import org.apache.ibatis.type.*;
import java.math.BigDecimal;
import java.sql.*;

// 使用注解声明处理的类型(非必须但推荐)
@MappedTypes(BigDecimal.class)
@MappedJdbcTypes(JdbcType.DECIMAL)
public class PlainBigDecimalTypeHandler extends BaseTypeHandler<BigDecimal> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, BigDecimal parameter, JdbcType jdbcType) 
        throws SQLException {
        ps.setBigDecimal(i, parameter);
    }

    @Override
    public BigDecimal getNullableResult(ResultSet rs, String columnName) 
        throws SQLException {
        return rs.getBigDecimal(columnName);
    }

    // 其他必要方法...

    // 重点:覆盖结果转换逻辑
    @Override
    public Object getResult(CallableStatement cs, int columnIndex) throws SQLException {
        BigDecimal value = cs.getBigDecimal(columnIndex);
        return value != null ? value.toPlainString() : null; // 使用 toPlainString()
    }
}

一、全局生效(所有匹配字段自动处理)

方法 1:通过配置类注册(推荐)

import org.apache.ibatis.session.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisConfig {

    @Bean
    public ConfigurationCustomizer typeHandlerRegistry() {
        return configuration -> {
            // 注册 BigDecimal 处理器(全局生效)
            configuration.getTypeHandlerRegistry().register(PlainBigDecimalTypeHandler.class);
            
            // 注册其他全局处理器
            configuration.getTypeHandlerRegistry().register(CustomDateTypeHandler.class);
        };
    }
}

方法 2:通过配置文件扫描

properties 配置文件

# application.properties
# 扫描整个包下的处理器(全局生效)
mybatis.type-handlers-package=com.example.handlers

全局生效特点:

  • 自动应用于所有匹配的字段类型
  • 无需在 Mapper XML 中额外配置
  • 处理范围由 @MappedTypes@MappedJdbcTypes 注解决定

二、局部生效(特定字段/查询生效)

方法 1:在 Mapper XML 中显式指定

<!-- UserMapper.xml -->
<resultMap id="userResult" type="User">
  <!-- 其他字段... -->
  
  <!-- 仅此字段使用自定义处理器 -->
  <result column="sale_price" property="salePrice" 
          typeHandler="com.example.handlers.PlainBigDecimalTypeHandler"/>
</resultMap>

<select id="selectUser" resultMap="userResult">
  SELECT * FROM user
</select>

方法 2:在注解方式中指定

public interface UserMapper {
    @Results({
        @Result(column = "sale_price", property = "salePrice", 
                typeHandler = PlainBigDecimalTypeHandler.class)
    })
    @Select("SELECT * FROM user")
    List<User> selectAll();
}

在 MyBatis 中,若要对多个字段使用类型处理器,可在 @Results 注解里添加多个 @Result 元素。每个 @Result 注解能针对不同的字段进行单独配置。

public interface UserMapper {
    @Results({
        @Result(column = "sale_price", property = "salePrice", 
                typeHandler = PlainBigDecimalTypeHandler.class),
        @Result(column = "original_price", property = "originalPrice", 
                typeHandler = PlainBigDecimalTypeHandler.class),
        @Result(column = "discount_rate", property = "discountRate", 
                typeHandler = PlainBigDecimalTypeHandler.class)
    })
    @Select("SELECT * FROM user")
    List<User> selectAll();
}

说明

  • 多个 @Result 元素:在 @Results 注解的数组里,可以添加任意数量的 @Result 注解。
  • 字段独立配置:每个 @Result 注解都能对 column(数据库列名)、property(实体类属性名)以及 typeHandler(类型处理器)进行单独配置。
  • 相同类型处理器复用:当多个字段需要使用相同的类型处理器时,比如示例中的 PlainBigDecimalTypeHandler,可以为每个字段分别指定该处理器。

三、条件生效(基于特定规则)

场景:仅处理特定表的字段

public class TableAwareBigDecimalHandler extends PlainBigDecimalTypeHandler {
    
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, 
                                    BigDecimal parameter, JdbcType jdbcType) throws SQLException {
        // 获取当前执行的SQL信息
        MetaObject metaObject = SystemMetaObject.forObject(ps);
        Statement stmt = (Statement) metaObject.getValue("delegate.statement");
        String sql = stmt.toString().toLowerCase();
        
        // 仅当是user表时才使用自定义处理
        if (sql.contains("from user")) {
            super.setNonNullParameter(ps, i, parameter, jdbcType);
        } else {
            ps.setBigDecimal(i, parameter); // 默认处理
        }
    }
}

四、范围控制最佳实践

1. 全局基础处理器 + 局部覆盖

@Configuration
public class MyBatisConfig {
    
    @Bean
    public ConfigurationCustomizer typeHandlerRegistry() {
        return config -> {
            // 注册基础处理器(全局)
            config.getTypeHandlerRegistry().register(DefaultBigDecimalHandler.class);
            
            // 注册特殊处理器(需在XML中显式使用)
            config.getTypeHandlerRegistry().register("specialHandler", SpecialBigDecimalHandler.class);
        };
    }
}

在 XML 文件中:

<!-- 使用特殊处理器 -->
<result column="sale_price" property="salePrice" typeHandler="specialHandler"/>

2. 基于包路径的范围控制

@Bean
public ConfigurationCustomizer typeHandlerRegistry() {
    return config -> {
        TypeHandlerRegistry registry = config.getTypeHandlerRegistry();
        
        // 只注册特定包下的处理器
        if (isFinanceModuleEnabled()) {
            registry.register("com.example.handlers.finance.*");
        }
        
        // 公共处理器
        registry.register(CommonDateHandler.class);
    };
}

五、生效范围验证方法

@SpringBootTest
class TypeHandlerTests {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    void testHandlerRegistration() {
        TypeHandlerRegistry registry = sqlSessionFactory.getConfiguration()
                                                      .getTypeHandlerRegistry();
        
        // 验证全局处理器
        assertTrue(registry.hasTypeHandler(BigDecimal.class));
        
        // 验证特定处理器
        TypeHandler<?> handler = registry.getTypeHandler(BigDecimal.class, JdbcType.DECIMAL);
        assertInstanceOf(PlainBigDecimalTypeHandler.class, handler);
        
        // 打印所有注册的处理器
        registry.getTypeHandlers().forEach(h -> 
            System.out.println("Registered: " + h.getClass().getName()));
    }
}

六、范围控制注意事项

  1. 优先级顺序:(很重要

    • 显式指定的处理器(XML/注解) > 全局注册的处理器
    • 最后注册的处理器会覆盖先前注册的
  2. 避免冲突

    // 精确指定处理类型避免冲突
    @MappedTypes(value = {BigDecimal.class})
    @MappedJdbcTypes(value = {JdbcType.DECIMAL}, includeNullJdbcType = true)
    public class FinanceBigDecimalHandler extends BaseTypeHandler<BigDecimal> {
        // 仅处理财务模块的DECIMAL字段
    }
    
  3. 模块化注册

    @Bean
    @ConditionalOnProperty(name = "module.finance.enabled", havingValue = "true")
    public ConfigurationCustomizer financeTypeHandlers() {
        return config -> {
            config.getTypeHandlerRegistry().register(FinanceBigDecimalHandler.class);
            config.getTypeHandlerRegistry().register(CurrencyTypeHandler.class);
        };
    }
    

七、不同范围方案对比

生效范围实现方式适用场景
全局所有字段配置类注册/包扫描基础类型转换(如日期、枚举)
特定字段XML/注解显式指定特殊字段需要特殊处理
特定表/模块条件注册+处理器内部逻辑判断多模块项目中的差异化处理
特定查询类型自定义Executor拦截器只对SELECT查询生效等复杂场景
基于环境配置@Conditional 条件注册不同环境使用不同转换逻辑

完整示例:财务模块专用处理器

// 1. 创建条件处理器
@MappedTypes(BigDecimal.class)
@MappedJdbcTypes(JdbcType.DECIMAL)
public class FinanceBigDecimalHandler extends BaseTypeHandler<BigDecimal> {
    
    private static final int FINANCE_SCALE = 4;

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, 
                                  BigDecimal parameter, JdbcType jdbcType) {
        // 财务字段固定4位小数
        BigDecimal value = parameter.setScale(FINANCE_SCALE, RoundingMode.HALF_UP);
        ps.setBigDecimal(i, value);
    }

    @Override
    public BigDecimal getNullableResult(ResultSet rs, String columnName) {
        return format(rs.getBigDecimal(columnName));
    }

    private BigDecimal format(BigDecimal value) {
        return value != null ? 
            value.setScale(FINANCE_SCALE, RoundingMode.HALF_UP) : null;
    }
    // ... 其他方法
}

// 2. 条件注册
@Configuration
@ConditionalOnProperty(name = "app.module.finance", havingValue = "true")
public class FinanceModuleConfig {
    
    @Bean
    public ConfigurationCustomizer financeTypeHandlers() {
        return config -> {
            config.getTypeHandlerRegistry().register(FinanceBigDecimalHandler.class);
        };
    }
}

通过以上策略,可以灵活控制自定义类型处理器的生效范围,满足从全局统一处理到字段级精细控制的各种需求场景。

JAVA-技能点
Mysql
Mybatis