在编写*Mapper.xml时
<insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User">
insert into user (id, user_name, pass_word,
real_name, name)
values (#{id,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},
#{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
</insert>
一般会写一个parameterType和jdbcType
那么这个parameterType起什么作用?
那么看这个insert过程来研究
直接看关键方法
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
public class ParameterMapping {
private Configuration configuration;
private String property;
private ParameterMode mode;
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;
有两个重要的属性,分别是javaType,和jdbcType
javaType代表入参java类型
jdbcType是对应数据库类型
可以分别指定
#{userName,javaType=java.lang.String, jdbcType=VARCHAR}
那么这两个type的作用是什么
用于根据入参和反参获取对用的TypeHandler
那么问题来了,什么是TypeHandler
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the result.
*
* @param rs
* the rs
* @param columnName
* Colunm name, when configuration useColumnLabel
is false
* @return the result
* @throws SQLException
* the SQL exception
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
其中 setParameter 方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet(根据列名或者索引位置获取) 或 CallableStatement(根据存储过程获取) 中取出数据转换为 java 对象。
主要是实现 从resultSet中根据columnName或者columnIndex来获取数据
当我们在javabean中自定义了枚举类型或者其它某个类型,但是在数据库中存储时往往需要转换成数据库对应的类型,并且在从数据库中取出来时也需要将数据库类型转换为javabean中的对应类型。比如:javabean中字段类型为Date,数据库中存储的是varchar类型;javabean中字段类型是Enum,数据库中存储的是String或者Integer。
因为有大量类似数据的转换,手动转换类型进行存储和查询已经过于麻烦。MyBatis为我们提供了解决办法:TypeHandler类型处理器。
MyBatis 中的 TypeHandler 类型处理器用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了大部分基本类型的类型处理器,所以对于基本类型可以直接处理,当我们需要处理其他类型的时候就需要自定义类型处理器。
MyBatis 内置的 TypeHandler
在 MyBatis 的 TypeHandlerRegistry 类型中,可以看到内置的类型处理器。内置处理器比较多,这里整理常见的一些。
BooleanTypeHandler:用于 java 类型 boolean,jdbc 类型 bit、boolean
ByteTypeHandler:用于 java 类型 byte,jdbc 类型 TINYINT
ShortTypeHandler:用于 java 类型 short,jdbc 类型 SMALLINT
IntegerTypeHandler:用于 INTEGER 类型
LongTypeHandler:用于 long 类型
FloatTypeHandler:用于 FLOAT 类型
DoubleTypeHandler:用于 double 类型
StringTypeHandler:用于 java 类型 string,jdbc 类型 CHAR、VARCHAR
ArrayTypeHandler:用于 jdbc 类型 ARRAY
BigDecimalTypeHandler:用于 java 类型 BigDecimal,jdbc 类型 REAL、DECIMAL、NUMERIC
DateTypeHandler:用于 java 类型 Date,jdbc 类型 TIMESTAMP
DateOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 DATE
TimeOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 TIME
对于常见的 Enum 类型,内置了 EnumTypeHandler 进行 Enum 名称的转换和 EnumOrdinalTypeHandler 进行 Enum 序数的转换。这两个类型处理器没有在 TypeHandlerRegistry 中注册,如果需要使用必须手动配置。
自定义类型处理器是通过实现 org.apache.ibatis.type.TypeHandler 接口实现的。这个接口定义了类型处理器的基本功能,接口定义如下所示。
其中 setParameter 方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet(根据列名或者索引位置获取) 或 CallableStatement(根据存储过程获取) 中取出数据转换为 java 对象。
实际开发中,我们可以继承 org.apache.ibatis.type.BaseTypeHandler 类型来实现自定义类型处理器。这个类型是抽象类型,实现了 TypeHandler 的方法进行通用流程的封装,做了异常处理,并定义了几个类似的抽象方法,如下所示。继承 BaseTypeHandler 类型可以极大地降低开发难度。
实现BaseTypeHandler 指定泛型就是对应的javaType
把TypeHandler配置到程序中有三种方法:
1.在Mapper.xml中声明
<result column="enum1" jdbcType="INTEGER" property="enum1" typeHandler="com.xxx.handler.EnumTypeHandler"/>
2.在mybatis配置文件中设置
<typeHandlers>
<typeHandler handler="com.xxx.handler.EnumTypeHandler"/>
</typeHandlers>
3.在springboot的yml配置文件中设置类型处理器所在的包名
mybatis:
type-handlers-package: com.xxx.handler
注册TypeHandler可以指定jdbcType也可以不指定
org.apache.ibatis.type.TypeHandlerRegistry#register(java.lang.reflect.Type, org.apache.ibatis.type.JdbcType, org.apache.ibatis.type.TypeHandler>)
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
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);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
mybatis内置了很多typeHandler在启动时注册
public TypeHandlerRegistry(Configuration configuration) {
this.unknownTypeHandler = new UnknownTypeHandler(configuration);
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new StringTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, unknownTypeHandler);
register(Object.class, JdbcType.OTHER, unknownTypeHandler);
register(JdbcType.OTHER, unknownTypeHandler);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
register(Instant.class, new InstantTypeHandler());
register(LocalDateTime.class, new LocalDateTimeTypeHandler());
register(LocalDate.class, new LocalDateTypeHandler());
register(LocalTime.class, new LocalTimeTypeHandler());
register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
register(OffsetTime.class, new OffsetTimeTypeHandler());
register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
register(Month.class, new MonthTypeHandler());
register(Year.class, new YearTypeHandler());
register(YearMonth.class, new YearMonthTypeHandler());
register(JapaneseDate.class, new JapaneseDateTypeHandler());
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
// #591
handler = pickSoleHandler(jdbcHandlerMap);
}
}
// type drives generics here
return (TypeHandler<T>) handler;
}
先通过javaType获取对应的,所有的handler,在根据jdbcType进一步匹配。也就是说,不同 javaType,jdbcType可以指定不同的handler。如果没注册对应的jdbcType,那么使用key为null(通用)的handler处理
前面说了每个字段可以指定javaType,和jdbcType
那么parmeterType的作用就是自动指定javaType,例如
<insert id="insert" parameterType="com.mybatis.cache.mybatiscache.domain.User">
insert into user (id, user_name, pass_word,
real_name, name)
values (#{id,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},
#{realName,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR})
</insert>
public class User {
private Integer id;
private String userName;
private String passWord;
private String realName;
private String name;
那么这些字段的javaType会自动匹配到语句的字段上,不需要挨个指定。
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;
}
也就是说javaType不指定也没什么关系,只是后面每次执行都要判断一次,从效率的角度看,还是在xml中能够指定的好,指定jdbcType后也会对入参类型进行检查,如果类型步匹配会报错。
nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping:
····
根据前面的分析。mybatis不会自动推断jdbcType,如果不指定就会尝试使用jdbcType对应默认的typeHandler处理。因此dbcType还是有必要指定的
对结果转换处理时根据resultMap进行转换的
<resultMap id="BaseResultMap" type="com.mybatis.cache.mybatiscache.domain.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="user_name" jdbcType="VARCHAR" property="userName" />
<result column="pass_word" jdbcType="VARCHAR" property="passWord" />
<result column="real_name" jdbcType="VARCHAR" property="realName" />
<result column="name" jdbcType="VARCHAR" property="name" />
</resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user
where id = #{id,jdbcType=INTEGER}
</select>
可见,对于结果的处理,也是根据jdbcType和JavaType进行处理
type实体类解析出每个字段的javaType,同时指定了jdbcType
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
·····
}
获取ResultMap和行记录进行处理
先看org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
先介绍下rowBounds是mybatis提供的分页工具用法如下
定义:List<A> testLimit(RowBounds rowBounds);
使用:testMapper.testLimit(new RowBounds(0, 10))
先根据RowBounds的offset跳过行
对于每一行数据,调用getRowValue进行处理
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//根据type创建实例
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix)
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnP
return rowValue;
}
创建实例之后,进入applyPropertyMappings
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
简化来看就是根据propertyMappings处理resultSet中的数据。根据javaType和jdbcType完成数据转换
完成之后设置值到对象上
处理完一行记录后
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
public class DefaultResultHandler implements ResultHandler<Object> {
private final List<Object> list;
public DefaultResultHandler() {
list = new ArrayList<>();
}
@SuppressWarnings("unchecked")
public DefaultResultHandler(ObjectFactory objectFactory) {
list = objectFactory.create(List.class);
}
@Override
public void handleResult(ResultContext<?> context) {
list.add(context.getResultObject());
}
public List<Object> getResultList() {
return list;
}
}
默认的实现就是将结果放到list中
最终返回的就是这个list中的数据。
resultType是sql映射文件中定义一行返回值类型,返回值有基本类型,对象类型,List类型,Map类型等。
resultType=基本类型(使用resultSet的每一行的第一列数据作为jdbcType,javaType就是指定的基本类型)
<select id="selectAll" resultType="java.lang.Integer">
select
<include refid="Base_Column_List" />
from user
</select>
这样的返回值是每一行的第一列Id值
: resultType=List中元素的类型
List<User> selectAll();
<select id="selectAll" resultType="com.mybatis.cache.mybatiscache.domain.User">
select
<include refid="Base_Column_List" />
from user
</select>
对于map类型那么缺失了javaType,那么返回的字段类型都是根据jdbcTyp推断来的
单条记录:resultType =map
Map selectAll();
<select id="selectAll" resultType="java.util.Map">
select
<include refid="Base_Column_List" />
from user
</select>
多条记录:resultType =Map中value的类型
List<Map> selectAll();
对于对象类型resultType直接写对象的全类名就可以了
不过需要注意的是对于返回对象没有resultMapping,mybatis会自动建立映射,但是只有resultType指定的类中的字段与resultSet中的字段完全对上的时候才会生成对应映射