① MyBatis 为简化配置文件提供了别名机制,该机制是类型转换模块的主要功能之一。
② 类型转换模块的另一个功能是实现 JDBC 类型与 Java 类型之间的转换,该功能在为 SQL 语句绑定实参以及映射查询结果集时都会涉及:
- 在为 SQL 语句绑定实参时,会将数据由 Java 类型转换成 JDBC 类型。
- 而在映射结果集时,会将数据由 JDBC 类型转换成 Java 类型。
类型处理器的作用如下图所示
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<String> {
@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);
}
}
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
typeHandlers>
使用上述的类型处理器将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器。 要注意 MyBatis 不会通过检测数据库元信息来决定使用哪种类型,所以你必须在参数和结果映射中指明字段是 VARCHAR 类型, 以使其能够绑定到正确的类型处理器上。这是因为 MyBatis 直到语句被执行时才清楚数据类型。
通过类型处理器的泛型,MyBatis 可以得知该类型处理器处理的 Java 类型,不过这种行为可以通过两种方法改变:
javaType
属性(比如:javaType="String"
);@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 类型只有一个注册的类型处理器,即使没有设置 includeNullJdbcType=true
,那么这个类型处理器也会是 ResultMap
使用 Java 类型时的默认处理器。
最后,可以让 MyBatis 帮你查找类型处理器:
<typeHandlers>
<package name="org.mybatis.example"/>
typeHandlers>
注意在使用自动发现功能的时候,只能通过注解方式来指定 JDBC 的类型。
你可以创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一个接受该类的 class 作为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例的时候传入一个具体的类。
//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
private Class<E> type;
public GenericTypeHandler(Class<E> 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
将通过他们的序数值来映射成对应的整形数值。
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
typeHandlers>
但要是你想在一个地方将 Enum
映射成字符串,在另外一个地方映射成整形值呢?
自动映射器(auto-mapper)会自动地选用 EnumOrdinalTypeHandler
来处理枚举类型, 所以如果我们想用普通的 EnumTypeHandler
,就必须要显式地为那些 SQL 语句设置要使用的类型处理器。
(下一节才开始介绍映射器文件,如果你是首次阅读该文档,你可能需要先跳过这里,过会再来看。)
<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
<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>
mapper>
注意,这里的 select 语句必须指定 resultMap
而不是 resultType
。
TypeHandler接口中定义了两种类型的方法
public interface TypeHandler<T> {
//设置入参参数
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//根据字段名获取结果集
T getResult(ResultSet rs, String columnName) throws SQLException;
//获得 ResultSet 指定索引的字段的值
T getResult(ResultSet rs, int columnIndex) throws SQLException;
//获取CallableStatement中指定索引的字段的值,CallableStatement可以调用存储过程
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
TypeHandler接口的抽象实现类,该实现类用于处理参数和类型为null的边界情况,正常情况的处理都交给具体的子类去实现。
/**
* 模板设计模式
*/
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
/**
* 已过时
*/
@Deprecated
protected Configuration configuration;
/**
* 已过时
*/
@Deprecated
public void setConfiguration(Configuration c) {
this.configuration = c;
}
//处理边界情况
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
//当参数为空时,调用 PreparedStatement对象的setNull
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
//调用抽象方法,具体逻辑交给子类实现
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
//从结果集中根据列名获取结果
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
}
//根据列的index获取结果
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e);
}
}
//从CallableStatement中获取结果
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e);
}
}
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the nullable result.
*
* @param rs
* the rs
* @param columnName
* Colunm name, when configuration useColumnLabel
is false
* @return the nullable result
* @throws SQLException
* the SQL exception
*/
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
}
在type包中继承自BaseTypeHandler的类非常多,这里找三个实现类来学习其源码
/**
* @author Clinton Begin
* 直接调用JDBC中和int类型有关的API即可
*/
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
int result = rs.getInt(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
int result = cs.getInt(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
对象类型的处理器会调用PreparedStatement中和Object有关的API,那么问题来了,当我传入一个Object类型的时候,PreparedStatement是如何知道我传入的到底是一个Integer还是一个String还是其他类型的?
下图截自mysql数据的JDBC驱动的AbstractQueryBindings类,由此可见数据库的驱动中会通过instance判断传入对象的实际类型,并对其进行转换。
既然已经有了这么多转换的方法,为什么Mybatis还要写这么多处理器?所有的请求都直接调用PreparedStatement#setObject方法不香吗?个人理解是,驱动中支持的类型有限,并且是用if else语句写死在代码里的,如果相对其进行扩展就显得比较僵硬,所以作者写了type包下的这么多实现类,而PreparedStatement中和对象有关的API则作为一个兜底的方案,用于处理type包中未提供处理器的类型。
//调用和Object有关的API
public class ObjectTypeHandler extends BaseTypeHandler<Object> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
ps.setObject(i, parameter);
}
@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getObject(columnName);
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getObject(columnIndex);
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getObject(columnIndex);
}
}
即使在org.apache.ibatis.type包中定义了如此多的类型处理器,但是缺少一种通用的处理器,即org.apache.ibatis.type包中定义的处理器都是用于处理特定类型,缺少一种能够根据参数的实际类型自动调用对应类型拦截器的方法的类,UnknownTypeHandler的出现便是为了解决这个尴尬的问题。通过持有TypeHandlerRegistry对象来判断具体使用哪一种类型处理器。
public class UnknownTypeHandler extends BaseTypeHandler<Object> {
//兜底的处理器,不知道该用什么处理器时就用ObjectTypeHandler
private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler();
private final Configuration config;
private final Supplier<TypeHandlerRegistry> typeHandlerRegistrySupplier;
public UnknownTypeHandler(Configuration configuration) {
this.config = configuration;
this.typeHandlerRegistrySupplier = configuration::getTypeHandlerRegistry;
}
@Deprecated
public UnknownTypeHandler(TypeHandlerRegistry typeHandlerRegistry) {
this.config = new Configuration();
this.typeHandlerRegistrySupplier = () -> typeHandlerRegistry;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}
@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
TypeHandler<?> handler = resolveTypeHandler(rs, columnName);
return handler.getResult(rs, columnName);
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
TypeHandler<?> handler = resolveTypeHandler(rs.getMetaData(), columnIndex);
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
return handler.getResult(rs, columnIndex);
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getObject(columnIndex);
}
private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<?> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}
private TypeHandler<?> resolveTypeHandler(ResultSet rs, String column) {
try {
Map<String,Integer> columnIndexLookup;
columnIndexLookup = new HashMap<>();
ResultSetMetaData rsmd = rs.getMetaData();
int count = rsmd.getColumnCount();
//是否使用列标签 select student_name as name from student
boolean useColumnLabel = config.isUseColumnLabel();
//构建columnIndexLookup
for (int i = 1; i <= count; i++) {
String name = useColumnLabel ? rsmd.getColumnLabel(i) : rsmd.getColumnName(i);
columnIndexLookup.put(name,i);
}
//获取列名column在结果集中的索引
Integer columnIndex = columnIndexLookup.get(column);
TypeHandler<?> handler = null;
if (columnIndex != null) {
handler = resolveTypeHandler(rsmd, columnIndex);
}
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
return handler;
} catch (SQLException e) {
throw new TypeException("Error determining JDBC type for column " + column + ". Cause: " + e, e);
}
}
private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) {
TypeHandler<?> handler = null;
//在JdbcType枚举类中查找对应列的jdbc类型
JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
//通过数据库驱动获取列在java中对应的类型
Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
//根据jdbcType和javaType的实际类型调用类型处理器的重载方法获取对应的处理器
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(jdbcType);
}
return handler;
}
private JdbcType safeGetJdbcTypeForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
try {
return JdbcType.forCode(rsmd.getColumnType(columnIndex));
} catch (Exception e) {
return null;
}
}
private Class<?> safeGetClassForColumn(ResultSetMetaData rsmd, Integer columnIndex) {
try {
return Resources.classForName(rsmd.getColumnClassName(columnIndex));
} catch (Exception e) {
return null;
}
}
}
定义了如此多的TypeHandler后接下来要考虑的问题是如何管理这些TypeHandler的实现类,mybatis在启动的时候会创建TypeHandlerRegistry对象,并将所有已知的TypeHandler实现类的实例注册到TypeHandlerRegistry对象中进行管理,方便后续的使用。
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler;
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
TypeHandlerRegistry的构造函数如下图所示,代码太长就不粘贴了,其功能为调用register的重载方法注册所有已知的TypeHandler实例。
接下来分析TypeHandlerRegistry的注册类方法,即register的各个重载方法。register方法的调用关系图如下:
方法虽然多,但是也遵循着从一般到具体的思路。
调用getInstance方法将实例化typeHandlerClass类
public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) {
register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));
}
实例化类型处理器对象
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
if (javaTypeClass != null) {
try {
//查找typeHandlerClass的单参构造函数,如果有则实例化。
//有单参数构造方法的有枚举类的类型处理器,如EnumTypeHandler或EnumOrdinalTypeHandler
Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
return (TypeHandler<T>) c.newInstance(javaTypeClass);
} catch (NoSuchMethodException ignored) {
// ignored 忽略异常
} catch (Exception e) {
throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
}
}
//如果没有单参数的构造函数,则调用类型处理器的默认构造函数进行实例化
try {
Constructor<?> c = typeHandlerClass.getConstructor();
return (TypeHandler<T>) c.newInstance();
} catch (Exception e) {
throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
}
}
public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
//将Class类型转换为Type类型
register((Type) type, jdbcType, handler);
}
关于Type类型,之前在学习Reflector包的时候说过,详见https://www.jianshu.com/p/cae76008b36b
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
//如果javaType不为空,则获取该类型对应的所有类型处理器
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
}
map.put(jdbcType, handler);
typeHandlerMap.put(javaType, map);//将javaType->handler的关系维护在typeHandlerMap中
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
}
和**register(Class, JdbcType, Class)**方法一样,调用getInstance方法实例化类型处理器
public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
}
再将javaType从Class类型转换成Type类型
public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
register((Type) javaType, typeHandler);
}
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
//判断typeHandler类是否被@MappedJdbcTypes注解修饰,改注解表示当前类型处理器用于处理何种JDBC类型的数据
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {//如果有@MappedJdbcTypes注解
//遍历调用register(Type, JdbcType, TypeHandler)方法注册类型处理器
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
//根据注解中的配置,注册jdbcType为null的类型拦截器
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
register(javaType, null, typeHandler);
}
}
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
//判断类型处理器是否被@MappedTypes注解修饰,该注解用于标识该类型处理器可以处理的java类型
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
public <T> void register(TypeHandler<T> typeHandler) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
//如果typeHandler类上有MappedTypes注解,则获取注解中配置的java类集合
if (mappedTypes != null) {
//遍历注册
for (Class<?> handledType : mappedTypes.value()) {
register(handledType, typeHandler);
mappedTypeFound = true;
}
}
// @since 3.1.0 - try to auto-discover the mapped type
// 如果该类型处理器没有被@MappedTypes注解修饰,但是却是TypeReference的子类,
// 因为TypeReference为一个泛型抽象类,当子类继承TypeReference时,
// 则可通过TypeReference类中泛型的实际类型来确定当前类型处理器可以处理的JavaType。
// 从而实现自动发现的功能。
// TypeReference类中定义的方法用于递归获取泛型的实际类型,其代码逻辑将会在未来的版本中
// 被Reflector包下的TypeParameterResolver类的resolveReturnType方法替代。
// TypeParameterResolver类之前在学习反射模块时已经分析过,不再过多BB。
if (!mappedTypeFound && typeHandler instanceof TypeReference) {
try {
TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
register(typeReference.getRawType(), typeHandler);
mappedTypeFound = true;
} catch (Throwable t) {
// maybe users define the TypeReference with a different type and are not assignable, so just ignore it
}
}
if (!mappedTypeFound) {//既没有被@MappedTypes注解修饰,又没有继承TypeReference
register((Class<T>) null, typeHandler);
}
}
public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) {
//获取TypeReference的实际类型,调用重载方法
register(javaTypeReference.getRawType(), handler);
}
直接注册
public void register(JdbcType jdbcType, TypeHandler<?> handler) {
jdbcTypeHandlerMap.put(jdbcType, handler);
}
getTypeHandler方法共有6个重载方法,其调用关系图为:
public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
return getTypeHandler((Type) type, null);
}
public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
return getTypeHandler((Type) type, jdbcType);
}
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
//获取type对应的所有类型处理器
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {//表示java类型有对应的类型拦截器
// 优先根据 type+jdbcType 确定TypeHandler
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {//如果没找到,则获取jdbcType为null的TypeHandler,兜底方案
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {//如果还是没找到,则选择第一个非空的
// #591
handler = pickSoleHandler(jdbcHandlerMap);
}
}
// type drives generics here
return (TypeHandler<T>) handler;
}
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
return null;
}
//如果没找到type对应的TypeHandler集合
if (jdbcHandlerMap == null && type instanceof Class) {
Class<?> clazz = (Class<?>) type;
//如果是枚举类型
if (Enum.class.isAssignableFrom(clazz)) {
//如果是匿名内部类,则获取其父类
Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
//实际获取TypeHandler集合的方法
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
if (jdbcHandlerMap == null) {
register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
return typeHandlerMap.get(enumClass);
}
} else {
//如果不是枚举类型,递归向上查询父类对应的TypeHandler集合
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
}
}
typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
return jdbcHandlerMap;
}
//递归获取枚举类的类型处理器
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
for (Class<?> iface : clazz.getInterfaces()) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(iface);
if (jdbcHandlerMap == null) {//如果没找到则继续递归
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
}
if (jdbcHandlerMap != null) {//找到了
// Found a type handler regsiterd to a super interface
HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
// Create a type handler instance with enum type as a constructor arg
//调用单参数的构造函数创建一个枚举类型的TypeHandler实例
newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
}
return newMap;
}
}
return null;
}
/**
* 递归向上查询父类对应的TypeHandler集合
**/
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class.equals(superclass)) {
return null;
}
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
if (jdbcHandlerMap != null) {
return jdbcHandlerMap;
} else {
return getJdbcHandlerMapForSuperclass(superclass);
}
}
类型与别名的注册表。通过别名,我们在 Mapper XML 中的
resultType
和parameterType
属性,直接使用,而不用写全类名。
代码很简单,直接看注释即可。
public class TypeAliasRegistry {
private final Map<String, Class<?>> typeAliases = new HashMap<>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
@SuppressWarnings("unchecked")
// throws class cast exception as well if types cannot be assigned
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
if (typeAliases.containsKey(key)) {
value = (Class<T>) typeAliases.get(key);
} else {
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
//注册指定包下的所有类,到Object为止
public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
//查找packageName包下所有superType的子类
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for (Class<?> type : typeSet) {
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
//跳过匿名内部类,接口和内部类
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
public void registerAlias(String alias, Class<?> value) {
//别名非空校验
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
//已经存在该别名,则报错
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
public void registerAlias(String alias, String value) {
try {
registerAlias(alias, Resources.classForName(value));
} catch (ClassNotFoundException e) {
throw new TypeException("Error registering type alias " + alias + " for " + value + ". Cause: " + e, e);
}
}
/**
* Gets the type aliases.
*
* @return the type aliases
* @since 3.2.2
*/
public Map<String, Class<?>> getTypeAliases() {
return Collections.unmodifiableMap(typeAliases);
}
}