### []( )类型处理器(typeHandlers) 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。 **提示** 从 3.4.5 开始,MyBatis 默认支持 JSR-310(日期和时间 API) 。 | 类型处理器 | Java 类型 | JDBC 类型 | | :-- | :-- | :-- | | `BooleanTypeHandler` | `java.lang.Boolean`, `boolean` | 数据库兼容的 `BOOLEAN` | | `ByteTypeHandler` | `java.lang.Byte`, `byte` | 数据库兼容的 `NUMERIC` 或 `BYTE` | | `ShortTypeHandler` | `java.lang.Short`, `short` | 数据库兼容的 `NUMERIC` 或 `SMALLINT` | | `IntegerTypeHandler` | `java.lang.Integer`, `int` | 数据库兼容的 `NUMERIC` 或 `INTEGER` | | `LongTypeHandler` | `java.lang.Long`, `long` | 数据库兼容的 `NUMERIC` 或 `BIGINT` | | `FloatTypeHandler` | `java.lang.Float`, `float` | 数据库兼容的 `NUMERIC` 或 `FLOAT` | | `DoubleTypeHandler` | `java.lang.Double`, `double` | 数据库兼容的 `NUMERIC` 或 `DOUBLE` | | `BigDecimalTypeHandler` | `java.math.BigDecimal` | 数据库兼容的 `NUMERIC` 或 `DECIMAL` | | `StringTypeHandler` | `java.lang.String` | `CHAR`, `VARCHAR` | | `ClobReaderTypeHandler` | `java.io.Reader` | \- | | `ClobTypeHandler` | `java.lang.String` | `CLOB`, `LONGVARCHAR` | | `NStringTypeHandler` | `java.lang.String` | `NVARCHAR`, `NCHAR` | | `NClobTypeHandler` | `java.lang.String` | `NCLOB` | | `BlobInputStreamTypeHandler` | `java.io.InputStream` | \- | | `ByteArrayTypeHandler` | `byte[]` | 数据库兼容的字节流类型 | | `BlobTypeHandler` | `byte[]` | `BLOB`, `LONGVARBINARY` | | `DateTypeHandler` | `java.util.Date` | `TIMESTAMP` | | `DateOnlyTypeHandler` | `java.util.Date` | `DATE` | | `TimeOnlyTypeHandler` | `java.util.Date` | `TIME` | | `SqlTimestampTypeHandler` | `java.sql.Timestamp` | `TIMESTAMP` | | `SqlDateTypeHandler` | `java.sql.Date` | `DATE` | | `SqlTimeTypeHandler` | `java.sql.Time` | `TIME` | | `ObjectTypeHandler` | Any | `OTHER` 或未指定类型 | | `EnumTypeHandler` | Enumeration Type | VARCHAR 或任何兼容的字符串类型,用以存储枚举的名称(而不是索引值) | | `EnumOrdinalTypeHandler` | Enumeration Type | 任何兼容的 `NUMERIC` 或 `DOUBLE` 类型,存储枚举的序数值(而不是名称)。 | | `SqlxmlTypeHandler` | `java.lang.String` | `SQLXML` | | `InstantTypeHandler` | `java.time.Instant` | `TIMESTAMP` | | `LocalDateTimeTypeHandler` | `java.time.LocalDateTime` | `TIMESTAMP` | | `LocalDateTypeHandler` | `java.time.LocalDate` | `DATE` | | `LocalTimeTypeHandler` | `java.time.LocalTime` | `TIME` | | `OffsetDateTimeTypeHandler` | `java.time.OffsetDateTime` | `TIMESTAMP` | | `OffsetTimeTypeHandler` | `java.time.OffsetTime` | `TIME` | | `ZonedDateTimeTypeHandler` | `java.time.ZonedDateTime` | `TIMESTAMP` | | `YearTypeHandler` | `java.time.Year` | `INTEGER` | | `MonthTypeHandler` | `java.time.Month` | `INTEGER` | | `YearMonthTypeHandler` | `java.time.YearMonth` | `VARCHAR` 或 `LONGVARCHAR` | | `JapaneseDateTypeHandler` | `java.time.chrono.JapaneseDate` | `DATE` | 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 `org.apache.ibatis.type.TypeHandler` 接口, 或继承一个很便利的类 `org.apache.ibatis.type.BaseTypeHandler`, 然后可以选择性地将它映射到一个 JDBC 类型。比如:
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
使用上述的类型处理器将会覆盖已经存在的处理 Java 的 String 类型属性和 VARCHAR 参数及结果的类型处理器。 要注意 MyBatis 不会通过窥探数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明那是 VARCHAR 类型的字段, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。 通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变: * 在类型处理器的配置元素(typeHandler 元素)上增加一个 `javaType` 属性(比如:`javaType="String"`); * 在类型处理器的类上(TypeHandler class)增加一个 `@MappedTypes` 注解来指定与其关联的 Java 类型列表。 如果在 `javaType` 属性中也同时指定,则注解方式将被忽略。 可以通过两种方式来指定被关联的 JDBC 类型: * 在类型处理器的配置元素上增加一个 `jdbcType` 属性(比如:`jdbcType="VARCHAR"`); * 在类型处理器的类上增加一个 `@MappedJdbcTypes` 注解来指定与其关联的 JDBC 类型列表。 如果在 `jdbcType` 属性中也同时指定,则注解方式将被忽略。 当在 `ResultMap` 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 `javaType=[Java 类型], jdbcType=null` 的组合来选择一个类型处理器。 这意味着使用 `@MappedJdbcTypes` 注解可以_限制_类型处理器的范围,同时除非显式的设置,否则类型处理器在 `ResultMap` 中将是无效的。 如果希望在 `ResultMap` 中使用类型处理器,那么设置 `@MappedJdbcTypes` 注解的 `includeNullJdbcType=true` 即可。 然而从 Mybatis 3.4.0 开始,如果**只有一个**注册的类型处理器来处理 Java 类型,那么它将是 `ResultMap` 使用 Java 类型时的默认值(即使没有 `includeNullJdbcType=true`)。 最后,可以让 MyBatis 为你查找类型处理器:
注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。 你可以创建一个能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样在构造一个类型处理器的时候 MyBatis 就会传入一个具体的类。
//GenericTypeHandler.java
public class GenericTypeHandler extends BaseTypeHandler {
private Class type;
public GenericTypeHandler(Class type) {
if (type == null) throw new IllegalArgumentException("Type argument cannot be null"); this.type = type;
}
…
`EnumTypeHandler` 和 `EnumOrdinalTypeHandler` 都是泛型类型处理器,我们将会在接下来的部分详细探讨。 ### []( )处理枚举类型 若想映射枚举类型 `Enum`,则需要从 `EnumTypeHandler` 或者 `EnumOrdinalTypeHandler` 中选一个来使用。 比如说我们想存储取近似值时用到的舍入模式。默认情况下,MyBatis 会利用 `EnumTypeHandler` 来把 `Enum` 值转换成对应的名字。 **注意 EnumTypeHandler 在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。** 不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样轻而易举: 在配置文件中把 `EnumOrdinalTypeHandler` 加到 `typeHandlers` 中即可, 这样每个 `RoundingMode` 将通过他们的序数值来映射成对应的整形数值。
但是怎样能将同样的 `Enum` 既映射成字符串又映射成整形呢? 自动映射器(auto-mapper)会自动地选用 `EnumOrdinalTypeHandler` 来处理, 所以如果我们想用普通的 `EnumTypeHandler`,就必须要显式地为那些 SQL 语句设置要使用的类型处理器。 (下一节才开始介绍映射器文件,如果你是首次阅读该文档,你可能需要先跳过这里,过会再来看。)
<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="funkyNumber" property="funkyNumber"/> <result column="roundingMode" property="roundingMode"/> </resultMap> <select id="getUser" resultMap="usermap"> select * from users </select> <insert id="insert"> insert into users (id, name, funkyNumber, roundingMode) values ( #{id}, #{name}, #{funkyNumber}, #{roundingMode} ) </insert> <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="funkyNumber" property="funkyNumber"/> <result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/> </resultMap> <select id="getUser2" resultMap="usermap2"> select * from users2 </select> <insert id="insert2"> insert into users2 (id, name, funkyNumber, roundingMode) values ( #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler} ) </insert>
注意,这里的 select 语句强制使用 `resultMap` 来代替 `resultType`。 ### []( )插件(plugins) MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) * ParameterHandler (getParameterObject, setParameters) * ResultSetHandler (handleResultSets, handleOutputParameters) * StatementHandler (prepare, parameterize, batch, update, query) 这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。 通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = “update”,
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性
CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】
光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性
CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】
[外链图片转存中…(img-WJjMEfeh-1630909615894)]
[外链图片转存中…(img-epI0YoGM-1630909615896)]
[外链图片转存中…(img-PfMgTgVA-1630909615897)]
[外链图片转存中…(img-6mytHVF0-1630909615899)]
[外链图片转存中…(img-mHdTkp7f-1630909615900)]