MyBatis 的拦截器(Interceptor)和自定义类型处理器(BaseTypeHandler 的子类)在功能、使用场景、使用方式和注意事项等方面有以下区别。
TypeHandler是MyBatis中用于处理Java类型与JDBC类型之间转换的接口。在SQL语句执行过程中,无论是设置参数还是获取结果集,都需要通过TypeHandler进行类型转换。MyBatis提供了丰富的内置TypeHandler实现,以支持常见的数据类型转换。同时,也可以根据需要自定义TypeHandler来处理特殊的数据类型或转换逻辑。
MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。MyBatis拦截器可以做的工作:SQL修改,分页操作,数据过滤,SQL执行时间性能监控等。
1. 功能区别
-
拦截器:
- 主要用于拦截 MyBatis 的四大核心对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)的特定方法执行,实现对 SQL 执行过程的干预。
- 可以在 SQL 执行的不同阶段进行操作,比如在执行 SQL 之前修改参数、在执行 SQL 之后处理结果等。
-
自定义类型处理器:
- 用于处理特定 Java 类型与特定数据库类型之间的转换。
- 专注于 Java 对象和数据库值之间的数据类型转换。
2. 使用场景
-
拦截器:
- 日志记录:记录 SQL 执行的时间、参数和结果,以便进行性能分析和故障排查。
- 缓存控制:实现自定义的缓存策略,提高查询性能。
- 安全控制:在 SQL 执行前进行权限检查,防止未经授权的访问。
- 动态 SQL 构建:根据特定条件动态修改 SQL 语句。
-
自定义类型处理器:
- 处理数据库中不常见的数据类型或自定义数据类型与 Java 对象之间的转换。
- 当 MyBatis 内置的类型处理器无法满足特定类型的转换需求时,例如处理特定格式的日期时间类型、自定义枚举类型等。
3. 原理分析
- 拦截器原理分析
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
概括一下,分别是拦截执行器、参数控制器、结果控制器、SQL语句构建控制器,对执行流程进行更改.对应四个对象.
- 字段类型处理器原理分析
自定义TypeHandler需要实现org.apache.ibatis.type.TypeHandler接口,或者继承org.apache.ibatis.type.BaseTypeHandler类,并重写相应的方法。
org.apache.ibatis.type.TypeHandler
TypeHandler是一个接口,用于定义如何处理JDBC类型和Java类型之间的转换。当你需要创建一个自定义的类型处理器时,通常需要实现这个接口。这个接口定义了以下主要方法:
setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType):用于设置PreparedStatement对象的指定参数。
getResult(ResultSet rs, String columnName):从结果集中根据列名获取数据。
getResult(ResultSet rs, int columnIndex):从结果集中根据列索引获取数据。
getResult(CallableStatement cs, int columnIndex):从存储过程的结果集中根据列索引获取数据。
这些方法分别负责在SQL语句执行时将Java类型的参数转换成JDBC类型,以及在执行SQL查询后将JDBC类型的结果转换成Java类型。
4. 使用方式
使用拦截器与BaseTypeHandler方式实现,将Java类型LocalDateTime(LocalDateTime默认纳秒9位,Time is represented to nanosecond precision.)转为MySQL类型timestamp。
-
拦截器:
- 定义一个实现
Interceptor
接口的类,并使用@Intercepts
注解指定要拦截的对象和方法。 - 在 MyBatis 配置文件中注册拦截器,或者通过编程方式将拦截器添加到
Configuration
对象中。
示例代码:
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import java.lang.reflect.Field; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Properties; @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), // @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class ParameterInterceptor implements Interceptor { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0]; Object parameter = null; BoundSql boundSql = mappedStatement.getBoundSql(parameter); // 获取参数 if (invocation.getArgs().length > 1) { parameter = invocation.getArgs()[1]; } // 获取 MetaObject,用于反射操作 MetaObject metaObject = SystemMetaObject.forObject(parameter); // 遍历所有字段 Field[] fields = parameter.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Object fieldValue = metaObject.getValue(field.getName()); if (fieldValue instanceof LocalDateTime) { LocalDateTime localDateTime = (LocalDateTime)fieldValue; String formattedDate = localDateTime.format(FORMATTER); metaObject.setValue(field.getName(), LocalDateTime.parse(formattedDate, FORMATTER)); } } // 继续执行原方法 return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } /** * 处理配置文件中的参数 * * @param properties 配置 */ @Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } }
全局字段转换在mybatis-config.xml配置文件中注册:
<configuration> <plugins> <plugin interceptor="your.package.MyInterceptor"> </plugin> </plugins> </configuration>
- 定义一个实现
-
自定义类型处理器:
- 继承
BaseTypeHandler
类,并实现其中的抽象方法,根据需要进行类型转换。 - 使用
@MappedJdbcTypes
和@MappedTypes
注解指定处理的 JDBC 类型和 Java 类型。 - 在 MyBatis 配置文件中注册类型处理器,或者通过编程方式将其添加到
Configuration
对象中。
示例代码:
import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @MappedJdbcTypes(JdbcType.TIMESTAMP) @MappedTypes(LocalDateTime.class) public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); @Override public void setNonNullParameter(PreparedStatement preparedStatement, int i, LocalDateTime localDateTime, JdbcType jdbcType) throws SQLException { String formattedDateTime = localDateTime.format(FORMATTER); Timestamp timestamp = Timestamp.valueOf(formattedDateTime); preparedStatement.setTimestamp(i, timestamp); } @Override public LocalDateTime getNullableResult(ResultSet resultSet, String columnName) throws SQLException { Long timestamp = resultSet.getLong(columnName); if (resultSet.wasNull()) { return null; } String timestampString = String.valueOf(timestamp); return LocalDateTime.parse(timestampString, FORMATTER); } @Override public LocalDateTime getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException { Long timestamp = resultSet.getLong(columnIndex); if (resultSet.wasNull()) { return null; } String timestampString = String.valueOf(timestamp); return LocalDateTime.parse(timestampString, FORMATTER); } @Override public LocalDateTime getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException { Long timestamp = callableStatement.getLong(columnIndex); if (callableStatement.wasNull()) { return null; } String timestampString = String.valueOf(timestamp); return LocalDateTime.parse(timestampString, FORMATTER); } }
- 继承
全局字段转换在mybatis-config.xml配置文件中注册:
<configuration>
<typeHandlers>
<typeHandler handler="your.package.LocalDateTimeTypeHandler"/>
</typeHandlers>
</configuration>
或者SqlSessionFactory 全局配置SqlSessionFactory.setTypeHandlers(new LocalDateTimeTypeHandler());
mapper.xml配置,单个字段转换,单个字段转换:指定jdbcType、javaType与typeHandler
<result property="orderTime" column="order_time" typeHandler="your.package.LocalDateTimeTypeHandler"/>
或者
#{orderTime,typeHandler=your.package.LocalDateTimeTypeHandler}
5. 注意事项
-
拦截器:
- 拦截器可能会影响性能,尤其是在频繁执行的 SQL 操作中,因为拦截器会增加额外的处理逻辑。
- 谨慎使用拦截器,确保拦截的逻辑不会破坏 MyBatis 的正常执行流程。
- 多个拦截器的执行顺序可能会影响结果,需要注意拦截器的注册顺序。
-
自定义类型处理器:
- 确保类型处理器的正确性和稳定性,特别是在处理边界情况和异常情况时。
- 不同的数据库对于特定类型的处理可能会有所不同,需要进行充分的测试以确保兼容性。
- 如果可能,尽量使用 MyBatis 内置的类型处理器,只有在必要时才创建自定义类型处理器。
6. 参考资料
https://www.bookstack.cn/read/MyBatis-zh-3.4/typeHandlers
https://blog.csdn.net/qq_26664043/article/details/136358934
https://cloud.tencent.com/developer/article/2398586
https://developer.aliyun.com/article/1549764
https://baomidou.com/guides/type-handler/
https://blog.csdn.net/wb1046329430/article/details/111501755
https://www.cnblogs.com/Courage129/p/14121453.html