Mybatis源码分析:类型处理器TypeHandler
类型处理器TypeHandler
TypeHandler是Mybatis中一个非常重要的接口,用于处理参数类型,包括入参形式和返回结果集相关参数的转换。该接口定义了以下方法。其方法实现已经由子类BaseTypeHandler已经实现了。
- void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
- T getResult(ResultSet rs, String columnName) throws SQLException;
- T getResult(ResultSet rs, int columnIndex) throws SQLException;
- T getResult(CallableStatement cs, int columnIndex) throws SQLException;
TypeHandler的继承关系和BaseTypeHandler中针对上述方法的实现。
可以看到mybatis提供了多种数据类型的实现用于针对Java到Sql数据类型的转换。这些数据类型包括byte,short,Integer,BigDecimal,float,double,long,String,enum,Object,bool,blob,clob和包括时间日期相关的处理,这些类型处理器主要是实现了BaseTypeHandler类中所定义的方法,因为BaseTypeHandler属于抽象类,在实现了接口方法的基础上,又添加了相关的扩展方法。首先看看BaseTypeHandler提供的源码实现,在实例化一个BaseTypeHandler的时候,必须提供Configuration配置类,这里顺带说一下。事实上Configuration类存储类你想要的任何配置信息和元数据信息,充当着的mybatis的全局容器,比如我们在写mapper文件或者配置文件时,通常将参数类型或者返回值类型简写,如下代码:
<select id="list" resultType="map" useCache="true" >
SELECT id,name,age from STUDENT
</select>
该配置将返回类型设置为map,于是mybatis会将查询结果以map形式展现。可以简写的原因是mybatis在实例化Configuration类时,初始化了一个别名注册器TypeAliasRegistry,别名注册器提供了数据类型简称和对应的数据类型。其构造方法代码如下所示,到这里就知道为什么我们只需要提供简称就行了,类型处理器会使用Configuration已经实例化好的别名注册器,寻找匹配的数据类型。除了TypeAliasRegistry别名注册器,Configuration还提供了很多的注册器,比如MapperRegistry,LanguageDriverRegistry,TypeHandlerRegistry等等。关于Configuration和注册器的知识后面再继续讲解。
1 public TypeAliasRegistry() { 2 registerAlias("string", String.class); 3 4 registerAlias("byte", Byte.class); 5 registerAlias("long", Long.class); 6 registerAlias("short", Short.class); 7 registerAlias("int", Integer.class); 8 registerAlias("integer", Integer.class); 9 registerAlias("double", Double.class); 10 registerAlias("float", Float.class); 11 registerAlias("boolean", Boolean.class); 12 13 registerAlias("byte[]", Byte[].class); 14 registerAlias("long[]", Long[].class); 15 registerAlias("short[]", Short[].class); 16 registerAlias("int[]", Integer[].class); 17 registerAlias("integer[]", Integer[].class); 18 registerAlias("double[]", Double[].class); 19 registerAlias("float[]", Float[].class); 20 registerAlias("boolean[]", Boolean[].class); 21 22 registerAlias("_byte", byte.class); 23 registerAlias("_long", long.class); 24 registerAlias("_short", short.class); 25 registerAlias("_int", int.class); 26 registerAlias("_integer", int.class); 27 registerAlias("_double", double.class); 28 registerAlias("_float", float.class); 29 registerAlias("_boolean", boolean.class); 30 31 registerAlias("_byte[]", byte[].class); 32 registerAlias("_long[]", long[].class); 33 registerAlias("_short[]", short[].class); 34 registerAlias("_int[]", int[].class); 35 registerAlias("_integer[]", int[].class); 36 registerAlias("_double[]", double[].class); 37 registerAlias("_float[]", float[].class); 38 registerAlias("_boolean[]", boolean[].class); 39 40 registerAlias("date", Date.class); 41 registerAlias("decimal", BigDecimal.class); 42 registerAlias("bigdecimal", BigDecimal.class); 43 registerAlias("biginteger", BigInteger.class); 44 registerAlias("object", Object.class); 45 46 registerAlias("date[]", Date[].class); 47 registerAlias("decimal[]", BigDecimal[].class); 48 registerAlias("bigdecimal[]", BigDecimal[].class); 49 registerAlias("biginteger[]", BigInteger[].class); 50 registerAlias("object[]", Object[].class); 51 52 registerAlias("map", Map.class); 53 registerAlias("hashmap", HashMap.class); 54 registerAlias("list", List.class); 55 registerAlias("arraylist", ArrayList.class); 56 registerAlias("collection", Collection.class); 57 registerAlias("iterator", Iterator.class); 58 59 registerAlias("ResultSet", ResultSet.class); 60 }
1 public Configuration() { 2 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); 3 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); 4 5 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); 6 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); 7 typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); 8 9 typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); 10 typeAliasRegistry.registerAlias("FIFO", FifoCache.class); 11 typeAliasRegistry.registerAlias("LRU", LruCache.class); 12 typeAliasRegistry.registerAlias("SOFT", SoftCache.class); 13 typeAliasRegistry.registerAlias("WEAK", WeakCache.class); 14 15 typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); 16 17 typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); 18 typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); 19 20 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); 21 typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); 22 typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); 23 typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); 24 typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); 25 typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); 26 typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); 27 28 typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); 29 typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); 30 31 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); 32 languageRegistry.register(RawLanguageDriver.class); 33 }
BaseTypeHandler
BaseTypeHandler是TypeHandler的实现类,除了实现了接口本身的方法外,还扩展了相关的方法,后续的各类型处理器均实现了BaseTypeHandler的抽象方法。首先看看BaseTypeHandler对接口方法的实现
setParameter()方法用于为PreparedStatement对象设置对应的参数类型,如果参数和JDBC类型均未设置,那么抛出TypeException,如果参数为空,那么设置参数类型为NULL,如果参数和JDBC都不为NULL,则设置指定的参数类型。在设置参数时,存在setNonNullParameter(ps, i, parameter, jdbcType);方法,该方法在BaseTypeHandler是抽象方法,假设该参数需要设置数组型参数,首先找到ArrayTypeHandler子类,观察方法中的实现.如下代码:
Array array = rs.getArray(columnName); return array == null ? null : array.getArray();
该实现方法中实例化一个数组对象,返回一个数组,可以推论出其他的类型处理器在设置参数时,也是设置对应的参数类型的
1 public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { 2 //如果参数和JDBC类型均未设置,那么抛出TypeException,如果参数为空,那么设置参数类型为NULL 3 if (parameter == null) { 4 if (jdbcType == null) { 5 throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); 6 } 7 try { 8 ps.setNull(i, jdbcType.TYPE_CODE); 9 } catch (SQLException e) { 10 throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " + 11 "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " + 12 "Cause: " + e, e); 13 } 14 } else { 15 try { 16 //设置指定的参数类型 17 setNonNullParameter(ps, i, parameter, jdbcType); 18 } catch (Exception e) { 19 throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " + 20 "Try setting a different JdbcType for this parameter or a different configuration property. " + 21 "Cause: " + e, e); 22 } 23 } 24 }
getResult()方法用于为设置字段对应的结果集,可以通过字段的名字和下标来设置对应的结果集,如下列代码所示,该方法也是BaseTypeHandler类的一个抽象方法,通过子类的getNullableResult()方法实现结果集的填充,仍以ArrayTypeHandler为例,查看相关的实现方法,可以看到ArrayTypeHandler只是做了对ResultSet简单的封装,想要获取什么样的数据类型就调用ResultSet中的方法。
1 public T getResult(ResultSet rs, String columnName) throws SQLException { 2 T result; 3 try { 4 result = getNullableResult(rs, columnName); 5 } catch (Exception e) { 6 throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e); 7 } 8 if (rs.wasNull()) { 9 return null; 10 } else { 11 return result; 12 } 13 }
1 @Override 2 public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { 3 Array array = rs.getArray(columnName); 4 return array == null ? null : array.getArray(); 5 } 6 7 @Override 8 public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException { 9 Array array = rs.getArray(columnIndex); 10 return array == null ? null : array.getArray(); 11 }
JdbcType:
在介绍设置参数的时候,涉及到JdbcType类,这个类本身是枚举类,所做的事情很简单,将java.sql.Types类包装成mybatis可用的JDBC类型,代码如下所示:
1 /** 2 * Copyright 2009-2015 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.apache.ibatis.type; 17 18 import java.sql.Types; 19 import java.util.HashMap; 20 import java.util.Map; 21 22 /** 23 * @author Clinton Begin 24 */ 25 public enum JdbcType { 26 /* 27 * This is added to enable basic support for the 28 * ARRAY data type - but a custom type handler is still required 29 */ 30 ARRAY(Types.ARRAY), 31 BIT(Types.BIT), 32 TINYINT(Types.TINYINT), 33 SMALLINT(Types.SMALLINT), 34 INTEGER(Types.INTEGER), 35 BIGINT(Types.BIGINT), 36 FLOAT(Types.FLOAT), 37 REAL(Types.REAL), 38 DOUBLE(Types.DOUBLE), 39 NUMERIC(Types.NUMERIC), 40 DECIMAL(Types.DECIMAL), 41 CHAR(Types.CHAR), 42 VARCHAR(Types.VARCHAR), 43 LONGVARCHAR(Types.LONGVARCHAR), 44 DATE(Types.DATE), 45 TIME(Types.TIME), 46 TIMESTAMP(Types.TIMESTAMP), 47 BINARY(Types.BINARY), 48 VARBINARY(Types.VARBINARY), 49 LONGVARBINARY(Types.LONGVARBINARY), 50 NULL(Types.NULL), 51 OTHER(Types.OTHER), 52 BLOB(Types.BLOB), 53 CLOB(Types.CLOB), 54 BOOLEAN(Types.BOOLEAN), 55 CURSOR(-10), // Oracle 56 UNDEFINED(Integer.MIN_VALUE + 1000), 57 NVARCHAR(Types.NVARCHAR), // JDK6 58 NCHAR(Types.NCHAR), // JDK6 59 NCLOB(Types.NCLOB), // JDK6 60 STRUCT(Types.STRUCT), 61 JAVA_OBJECT(Types.JAVA_OBJECT), 62 DISTINCT(Types.DISTINCT), 63 REF(Types.REF), 64 DATALINK(Types.DATALINK), 65 ROWID(Types.ROWID), // JDK6 66 LONGNVARCHAR(Types.LONGNVARCHAR), // JDK6 67 SQLXML(Types.SQLXML), // JDK6 68 DATETIMEOFFSET(-155); // SQL Server 2008 69 70 public final int TYPE_CODE; 71 private static Map<Integer,JdbcType> codeLookup = new HashMap<Integer,JdbcType>(); 72 73 static { 74 for (JdbcType type : JdbcType.values()) { 75 codeLookup.put(type.TYPE_CODE, type); 76 } 77 } 78 79 JdbcType(int code) { 80 this.TYPE_CODE = code; 81 } 82 83 public static JdbcType forCode(int code) { 84 return codeLookup.get(code); 85 } 86 87 }