让代码更优雅?Mybatis 类型处理器了解一下!
一、什么是类型处理器(TypeHandler)?
1.1 核心概念
类型处理器是 MyBatis 中用于处理 Java 类型与 JDBC 类型之间转换的核心组件。它解决了两个关键问题:
-
参数映射:将 Java 对象转换为 JDBC 参数
-
结果映射:将 ResultSet 中的数据转换为 Java 对象
java
// 简化的类型处理器接口 public interface TypeHandler{ // 设置 PreparedStatement 参数 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType); // 从 ResultSet 获取结果 T getResult(ResultSet rs, String columnName); T getResult(ResultSet rs, int columnIndex); T getResult(CallableStatement cs, int columnIndex); }
1.2 为什么需要类型处理器?
问题场景:
java
// 传统的做法:在业务代码中处理类型转换
public class UserMapper {
public User findById(Long id) {
User user = sqlSession.selectOne("findUserById", id);
// 手动转换 JSON 字段
String jsonAttr = user.getJsonAttr();
Map attributes = JSON.parse(jsonAttr);
user.setAttributes(attributes);
// 手动转换枚举
String statusStr = user.getStatus();
UserStatus status = UserStatus.valueOf(statusStr);
user.setStatusEnum(status);
return user;
}
}
使用类型处理器后:
java
public class UserMapper {
public User findById(Long id) {
// 所有转换自动完成
return sqlSession.selectOne("findUserById", id);
}
}
二、MyBatis 内置类型处理器
2.1 常用内置处理器
| Java 类型 | JDBC 类型 | 类型处理器 |
|---|---|---|
| String | VARCHAR, CHAR | StringTypeHandler |
| Integer | INTEGER | IntegerTypeHandler |
| Long | BIGINT | LongTypeHandler |
| Date | TIMESTAMP | DateTypeHandler |
| BigDecimal | DECIMAL | BigDecimalTypeHandler |
| boolean | BOOLEAN, BIT | BooleanTypeHandler |
| byte[] | BLOB, BINARY | BlobTypeHandler |
| Enum | VARCHAR | EnumTypeHandler |
| Enum | INTEGER | EnumOrdinalTypeHandler |
2.2 枚举处理器示例
java
// 枚举定义
public enum UserStatus {
ACTIVE("A", "活跃"),
INACTIVE("I", "禁用"),
LOCKED("L", "锁定");
private final String code;
private final String description;
UserStatus(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public static UserStatus fromCode(String code) {
for (UserStatus status : values()) {
if (status.code.equals(code)) {
return status;
}
}
throw new IllegalArgumentException("未知状态码: " + code);
}
}
// 自动使用 EnumTypeHandler(按名称存储)
// 数据库中存储 "ACTIVE", "INACTIVE" 等字符串
三、自定义类型处理器实战
3.1 继承 BaseTypeHandler
java
// JSON 类型处理器示例 @MappedTypes(Map.class) // 处理的 Java 类型 @MappedJdbcTypes(JdbcType.VARCHAR) // 处理的 JDBC 类型 public class JsonTypeHandler extends BaseTypeHandler
3.2 枚举代码映射处理器
java
// 自定义枚举处理器:按代码存储 public class UserStatusTypeHandler extends BaseTypeHandler{ @Override public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter.getCode()); } @Override public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException { String code = rs.getString(columnName); return rs.wasNull() ? null : UserStatus.fromCode(code); } @Override public UserStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String code = rs.getString(columnIndex); return rs.wasNull() ? null : UserStatus.fromCode(code); } @Override public UserStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String code = cs.getString(columnIndex); return cs.wasNull() ? null : UserStatus.fromCode(code); } }
3.3 日期时间处理器
java
// Java 8 时间处理器 public class LocalDateTimeTypeHandler extends BaseTypeHandler{ @Override public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, Timestamp.valueOf(parameter)); } @Override public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException { Timestamp timestamp = rs.getTimestamp(columnName); return timestamp != null ? timestamp.toLocalDateTime() : null; } @Override public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException { Timestamp timestamp = rs.getTimestamp(columnIndex); return timestamp != null ? timestamp.toLocalDateTime() : null; } @Override public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { Timestamp timestamp = cs.getTimestamp(columnIndex); return timestamp != null ? timestamp.toLocalDateTime() : null; } }
3.4 集合类型处理器
java
// 逗号分隔的字符串转 List public class StringListTypeHandler extends BaseTypeHandler> { private static final String DELIMITER = ","; @Override public void setNonNullParameter(PreparedStatement ps, int i, List
parameter, JdbcType jdbcType) throws SQLException { String value = String.join(DELIMITER, parameter); ps.setString(i, value); } @Override public List getNullableResult(ResultSet rs, String columnName) throws SQLException { String value = rs.getString(columnName); return parseString(value); } @Override public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String value = rs.getString(columnIndex); return parseString(value); } @Override public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String value = cs.getString(columnIndex); return parseString(value); } private List parseString(String value) { if (StringUtils.isBlank(value)) { return new ArrayList<>(); } return Arrays.asList(value.split(DELIMITER)); } }
四、配置类型处理器
4.1 XML 配置方式
xml
4.2 Spring Boot 配置
java
// 配置类方式
@Configuration
public class MybatisConfig {
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer() {
return configuration -> {
// 注册类型处理器
configuration.getTypeHandlerRegistry().register(
Map.class,
JdbcType.VARCHAR,
JsonTypeHandler.class
);
// 注册枚举处理器
configuration.getTypeHandlerRegistry().register(
UserStatus.class,
UserStatusTypeHandler.class
);
};
}
}
// 或者在 MapperScan 中指定
@MapperScan(
basePackages = "com.example.mapper",
typeHandlers = {JsonTypeHandler.class, UserStatusTypeHandler.class}
)
4.3 注解配置
java
// 在实体类字段上直接指定
public class User {
private Long id;
private String name;
@TableField(typeHandler = JsonTypeHandler.class)
private Map attributes;
@TableField(typeHandler = UserStatusTypeHandler.class)
private UserStatus status;
// getters and setters
}
五、在 Mapper 中使用类型处理器
5.1 XML Mapper 中使用
xml
INSERT INTO users (name, attributes, status) VALUES ( #{name}, #{attributes, typeHandler=com.example.handler.JsonTypeHandler}, #{status, typeHandler=com.example.handler.UserStatusTypeHandler} ) UPDATE users SET name = #{name}, attributes = #{attributes, typeHandler=com.example.handler.JsonTypeHandler}, status = #{status, typeHandler=com.example.handler.UserStatusTypeHandler} WHERE id = #{id}
5.2 注解方式使用
java
@Mapper
public interface UserMapper {
@Results({
@Result(column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "attributes", property = "attributes",
typeHandler = JsonTypeHandler.class),
@Result(column = "status", property = "status",
typeHandler = UserStatusTypeHandler.class)
})
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(Long id);
@Insert("INSERT INTO users (name, attributes, status) " +
"VALUES (#{name}, " +
"#{attributes, typeHandler=com.example.handler.JsonTypeHandler}, " +
"#{status, typeHandler=com.example.handler.UserStatusTypeHandler})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
}
六、高级应用场景
6.1 多数据库适配
java
// 支持多种数据库的 JSON 处理 public class MultiDatabaseJsonTypeHandler extends BaseTypeHandler> { private DatabaseType databaseType; private ObjectMapper objectMapper = new ObjectMapper(); public MultiDatabaseJsonTypeHandler() { // 可以通过配置文件或自动检测获取数据库类型 this.databaseType = detectDatabaseType(); } @Override public void setNonNullParameter(PreparedStatement ps, int i, Map parameter, JdbcType jdbcType) throws SQLException { try { String json = objectMapper.writeValueAsString(parameter); switch (databaseType) { case MYSQL: // MySQL 5.7+ 支持 JSON 类型 ps.setObject(i, json, Types.OTHER); break; case POSTGRESQL: // PostgreSQL 使用 jsonb ps.setObject(i, json, Types.OTHER); break; case ORACLE: // Oracle 使用 CLOB 或 VARCHAR2 if (json.length() > 4000) { ps.setClob(i, new StringReader(json)); } else { ps.setString(i, json); } break; default: ps.setString(i, json); } } catch (JsonProcessingException e) { throw new SQLException("JSON 序列化失败", e); } } // 省略其他方法... }
6.2 加密字段处理
java
// 数据库字段加密处理器 public class EncryptTypeHandler extends BaseTypeHandler{ private static final String AES_KEY = "your-secret-key"; private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { try { String encrypted = encrypt(parameter); ps.setString(i, encrypted); } catch (Exception e) { throw new SQLException("加密失败", e); } } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String encrypted = rs.getString(columnName); return encrypted != null ? decrypt(encrypted) : null; } private String encrypt(String data) throws Exception { Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(AES_KEY.getBytes(), "AES")); byte[] encrypted = cipher.doFinal(data.getBytes()); return Base64.getEncoder().encodeToString(encrypted); } private String decrypt(String encrypted) { try { Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(AES_KEY.getBytes(), "AES")); byte[] decoded = Base64.getDecoder().decode(encrypted); byte[] decrypted = cipher.doFinal(decoded); return new String(decrypted); } catch (Exception e) { throw new RuntimeException("解密失败", e); } } }
6.3 通用泛型处理器
java
// 通用 JSON 处理器,支持任意类型 public class GenericJsonTypeHandlerextends BaseTypeHandler { private final Class type; private final ObjectMapper objectMapper; public GenericJsonTypeHandler(Class type) { this.type = type; this.objectMapper = new ObjectMapper(); this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override public void setNonNullParameter(PreparedStatement ps, int i, T 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 T getNullableResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); return parseJson(json); } private T parseJson(String json) { if (StringUtils.isBlank(json)) { return null; } try { return objectMapper.readValue(json, type); } catch (IOException e) { throw new RuntimeException("JSON 解析失败: " + json, e); } } // 省略其他 getResult 方法 }
6.4 注册泛型处理器
java
// 自定义 TypeHandlerRegistry
@Component
public class CustomTypeHandlerRegistry {
@PostConstruct
public void registerGenericHandlers() {
// 创建通用处理器实例
TypeHandler> mapHandler =
new GenericJsonTypeHandler<>(Map.class);
TypeHandler> listHandler =
new GenericJsonTypeHandler<>(List.class);
TypeHandler configHandler =
new GenericJsonTypeHandler<>(UserConfig.class);
// 注册到 MyBatis
Configuration configuration = getMybatisConfiguration();
configuration.getTypeHandlerRegistry().register(
Map.class, JdbcType.VARCHAR, mapHandler);
configuration.getTypeHandlerRegistry().register(
List.class, JdbcType.VARCHAR, listHandler);
configuration.getTypeHandlerRegistry().register(
UserConfig.class, JdbcType.VARCHAR, configHandler);
}
}
七、最佳实践和注意事项
7.1 性能优化
java
// 使用缓存提升性能 public class CachedJsonTypeHandler extends BaseTypeHandler> { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ConcurrentHashMap > CACHE = new ConcurrentHashMap<>(); @Override public void setNonNullParameter(PreparedStatement ps, int i, Map parameter, JdbcType jdbcType) throws SQLException { try { String json = MAPPER.writeValueAsString(parameter); ps.setString(i, json); } catch (JsonProcessingException e) { throw new SQLException("JSON 序列化失败", e); } } @Override public Map getNullableResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); if (json == null) { return null; } // 缓存命中 Map cached = CACHE.get(json); if (cached != null) { return cached; } // 解析并缓存 Map result = parseJson(json); if (result != null) { CACHE.put(json, result); } return result; } // 省略其他方法... }
7.2 异常处理
java
// 增强的异常处理 public class SafeJsonTypeHandler extends BaseTypeHandler> { private static final ObjectMapper MAPPER = new ObjectMapper(); @Override public void setNonNullParameter(PreparedStatement ps, int i, Map parameter, JdbcType jdbcType) throws SQLException { try { String json = MAPPER.writeValueAsString(parameter); ps.setString(i, json); } catch (JsonProcessingException e) { // 记录日志但不抛出异常,使用空对象替代 log.error("JSON 序列化失败,使用空对象", e); try { ps.setString(i, "{}"); } catch (SQLException ex) { throw new SQLException("设置参数失败", ex); } } } @Override public Map getNullableResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); if (StringUtils.isBlank(json)) { return new HashMap<>(); } try { return MAPPER.readValue(json, new TypeReference >() {}); } catch (IOException e) { log.warn("JSON 解析失败,返回空对象: {}", json, e); return new HashMap<>(); } } // 省略其他方法... }
7.3 单元测试
java
// 类型处理器单元测试
@SpringBootTest
public class JsonTypeHandlerTest {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Test
public void testJsonTypeHandler() throws Exception {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 准备测试数据
User user = new User();
user.setName("张三");
Map attributes = new HashMap<>();
attributes.put("age", 25);
attributes.put("city", "北京");
attributes.put("hobbies", Arrays.asList("篮球", "音乐"));
user.setAttributes(attributes);
// 插入数据
mapper.insert(user);
session.commit();
// 查询数据
User retrieved = mapper.selectById(user.getId());
// 验证
assertNotNull(retrieved);
assertEquals("张三", retrieved.getName());
assertEquals(25, retrieved.getAttributes().get("age"));
assertEquals("北京", retrieved.getAttributes().get("city"));
// 验证 JSON 序列化/反序列化
String json = new ObjectMapper().writeValueAsString(attributes);
Map fromDb = retrieved.getAttributes();
String jsonFromDb = new ObjectMapper().writeValueAsString(fromDb);
assertEquals(json, jsonFromDb);
}
}
@Test
public void testNullHandling() {
JsonTypeHandler handler = new JsonTypeHandler();
// 测试空值处理
assertNull(handler.getNullableResult(null, 0));
// 测试空字符串
Map result = handler.parseJson("");
assertNotNull(result);
assertTrue(result.isEmpty());
}
}
八、Spring Boot 集成示例
8.1 完整项目结构
text
src/main/java/ ├── com.example.demo/ │ ├── config/ │ │ ├── MybatisConfig.java │ │ └── TypeHandlerConfig.java │ ├── handler/ │ │ ├── JsonTypeHandler.java │ │ ├── EncryptTypeHandler.java │ │ └── UserStatusTypeHandler.java │ ├── entity/ │ │ ├── User.java │ │ ├── UserStatus.java │ │ └── BaseEntity.java │ ├── mapper/ │ │ └── UserMapper.java │ ├── service/ │ │ └── UserService.java │ └── DemoApplication.java
8.2 配置类
java
// TypeHandlerConfig.java
@Configuration
public class TypeHandlerConfig {
@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration mybatisConfiguration() {
org.apache.ibatis.session.Configuration configuration =
new org.apache.ibatis.session.Configuration();
// 注册类型处理器
TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry();
// 注册自定义处理器
registry.register(JsonTypeHandler.class);
registry.register(EncryptTypeHandler.class);
registry.register(UserStatus.class, UserStatusTypeHandler.class);
// 注册通用处理器
registry.register(Map.class, JdbcType.VARCHAR,
new GenericJsonTypeHandler<>(Map.class));
registry.register(List.class, JdbcType.VARCHAR,
new GenericJsonTypeHandler<>(List.class));
return configuration;
}
@Bean
public SqlSessionFactory sqlSessionFactory(
DataSource dataSource,
org.apache.ibatis.session.Configuration configuration)
throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setConfiguration(configuration);
// 设置类型别名包
factory.setTypeAliasesPackage("com.example.demo.entity");
// 设置类型处理器包
factory.setTypeHandlersPackage("com.example.demo.handler");
return factory.getObject();
}
}
8.3 实体类示例
java
// User.java
@Data
@TableName("users")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
@TableField(typeHandler = JsonTypeHandler.class)
private Map attributes;
@TableField(typeHandler = EncryptTypeHandler.class)
private String phone;
@TableField(typeHandler = UserStatusTypeHandler.class)
private UserStatus status;
@TableField(typeHandler = LocalDateTimeTypeHandler.class)
private LocalDateTime createTime;
@TableField(typeHandler = LocalDateTimeTypeHandler.class)
private LocalDateTime updateTime;
}
8.4 服务层使用
java
// UserService.java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User createUser(UserDTO userDTO) {
User user = new User();
user.setName(userDTO.getName());
// 设置属性,类型处理器会自动转换
Map attributes = new HashMap<>();
attributes.put("age", userDTO.getAge());
attributes.put("email", userDTO.getEmail());
attributes.put("preferences", userDTO.getPreferences());
user.setAttributes(attributes);
// 敏感信息自动加密
user.setPhone(userDTO.getPhone());
// 枚举自动转换
user.setStatus(UserStatus.ACTIVE);
userMapper.insert(user);
return user;
}
public User getUserById(Long id) {
// 所有转换在 MyBatis 层自动完成
return userMapper.selectById(id);
}
}
九、常见问题与解决方案
9.1 问题1:类型处理器不生效
原因分析:
-
未正确注册类型处理器
-
Mapper XML 中未指定 typeHandler
-
Java 类型与处理器不匹配
解决方案:
java
// 调试代码
@SpringBootTest
public class TypeHandlerDebugTest {
@Test
public void debugTypeHandler() {
Configuration configuration = sqlSessionFactory.getConfiguration();
TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry();
// 检查处理器是否注册
TypeHandler> handler = registry.getTypeHandler(Map.class, JdbcType.VARCHAR);
System.out.println("注册的处理器: " + handler);
// 检查 MappedStatement
MappedStatement ms = configuration.getMappedStatement(
"com.example.mapper.UserMapper.selectById");
ResultMap resultMap = ms.getResultMaps().get(0);
for (ResultMapping mapping : resultMap.getPropertyResultMappings()) {
System.out.println("字段: " + mapping.getProperty() +
", 处理器: " + mapping.getTypeHandler());
}
}
}
9.2 问题2:性能问题
优化策略:
-
使用缓存
-
避免在循环中创建 ObjectMapper
-
使用连接池管理 PreparedStatement
java
// 性能优化示例 public class OptimizedJsonTypeHandler extends BaseTypeHandler> { // 使用静态变量避免重复创建 private static final ThreadLocal MAPPER_CACHE = ThreadLocal.withInitial(() -> { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return mapper; }); // 使用软引用缓存解析结果 private static final Map >> CACHE = new ConcurrentHashMap<>(); @Override public Map getNullableResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); if (json == null) { return null; } // 检查缓存 SoftReference > ref = CACHE.get(json); if (ref != null) { Map cached = ref.get(); if (cached != null) { return cached; } } // 解析并缓存 try { ObjectMapper mapper = MAPPER_CACHE.get(); Map result = mapper.readValue(json, new TypeReference >() {}); CACHE.put(json, new SoftReference<>(result)); return result; } catch (IOException e) { throw new SQLException("JSON 解析失败", e); } } }
9.3 问题3:版本兼容性
java
// 多版本兼容的处理器 public class VersionAwareTypeHandlerextends BaseTypeHandler { private final Map > versionHandlers = new HashMap<>(); public VersionAwareTypeHandler() { // 注册不同版本的处理器 versionHandlers.put("1.0", new V1TypeHandler()); versionHandlers.put("2.0", new V2TypeHandler()); } @Override public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { String version = getCurrentVersion(); TypeHandler handler = versionHandlers.get(version); if (handler != null) { handler.setParameter(ps, i, parameter, jdbcType); } else { // 使用默认处理器 ps.setObject(i, parameter); } } private String getCurrentVersion() { // 从配置文件或上下文获取版本号 return "2.0"; } }










