MyBatis源码阅读: MyBatis基础模块-类型转换模块
Posted 循环网络不循环
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis源码阅读: MyBatis基础模块-类型转换模块相关的知识,希望对你有一定的参考价值。
一、概述
MyBatis是一个持久层框架ORM框架,实现数据库中数据和Java对象中的属性的双向映射,那么不可避免的就会碰到类型转换的问题,在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换为Java类型,所以我们来看下在MyBatis中是如何实现类型的转换的。
二、源码阅读
(一)TypeHandler
MyBatis中的所有的类型转换器都继承了TypeHandler接口,在TypeHandler中定义了类型转换器的最基本的功能。
/**
* @author Clinton Begin
*/
public interface TypeHandler<T>
/**
* 负责将Java类型转换为JDBC的类型
* 本质上执行的就是JDBC操作中的 如下操作
* String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ? and user_name = ?";
* ps = conn.prepareStatement(sql);
* ps.setInt(1,2);
* ps.setString(2,"张三");
* @param ps
* @param i 对应占位符的 位置
* @param parameter 占位符对应的值
* @param jdbcType 对应的 jdbcType 类型
* @throws SQLException
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 从ResultSet中获取数据时会调用此方法,会将数据由JdbcType转换为Java类型
* @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
1、BaseTypeHandler
为了方便用户自定义TypeHandler的实现,在MyBatis中提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference类,在BaseTypeHandler中的实现方法中实现了对null的处理,非空的处理是交给各个子类去实现的。这个在代码中很清楚的体现了出来。
2、TypeHandler实现类
TypeHandler的实现类比较多,而且实现也都比较简单。
以Integer为例
/**
* @author Clinton Begin
*/
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;
(二)TypeHandlerRegistry
通过前面的介绍我们发现在MyBatis中给我们提供的具体的类型转换器实在是太多了,那么在实际的使用时我们是如何知道使用哪个转换器类处理的呢?实际上再MyBatis中是将所有的TypeHandler都保存注册在了TypeHandlerRegistry中的。首先注意声明的相关属性
// 记录JdbcType和TypeHandle的对应关系
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
// 记录Java类型向指定的JdbcType转换时需要使用到的TypeHandle
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler;
// 记录全部的TypeHandle类型及对应的TypeHandle对象
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
// 空TypeHandle的标识
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
然后在器构造方法中完成了系统提供的TypeHandler的注册
代码太长,请自行查阅。注意的是register()方法, 关键几个实现如下
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler)
// 获取@MappedJdbcTypes注解
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null)
// 遍历获取注解中指定的 JdbcType 类型
for (JdbcType handledJdbcType : mappedJdbcTypes.value())
// 调用下一个重载的方法
register(javaType, handledJdbcType, typeHandler);
if (mappedJdbcTypes.includeNullJdbcType())
// JdbcType类型为空的情况
register(javaType, null, typeHandler);
else
register(javaType, null, typeHandler);
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)
if (javaType != null) // 如果不为空
// 从 TypeHandle集合中根据Java类型来获取对应的集合
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP)
// 如果没有就创建一个新的
map = new HashMap<>();
// 把对应的jdbc类型和处理器添加到map集合中
map.put(jdbcType, handler);
// 然后将 java类型和上面的map集合保存到TypeHandle的容器中
typeHandlerMap.put(javaType, map);
// 同时也把这个处理器添加到了 保存有所有处理器的容器中
allTypeHandlersMap.put(handler.getClass(), handler);
有注册的方法,当然也有从注册器中获取TypeHandler的方法,getTypeHandler方法,这个方法也有多个重载的方法,这里重载的方法最终都会执行的方法是
/**
* 根据对应的Java类型和Jdbc类型来查找对应的TypeHandle
*/
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType)
if (ParamMap.class.equals(type))
return null;
// 根据Java类型获取对应的 Jdbc类型和TypeHandle的集合容器
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null)
// 根据Jdbc类型获取对应的 处理器
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null)
// 获取null对应的处理器
handler = jdbcHandlerMap.get(null);
if (handler == null)
// #591
handler = pickSoleHandler(jdbcHandlerMap);
// type drives generics here
return (TypeHandler<T>) handler;
(三)TypeAliasRegistry
我们在MyBatis的应用的时候会经常用到别名,这能大大简化我们的代码,其实在MyBatis中是通过TypeAliasRegistry类管理的。首先在构造方法中会注入系统常见类型的别名
注册的方法逻辑也比较简单
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() + "'.");
// 将 别名 和 类型 添加到 Map 集合中
typeAliases.put(key, value);
那么我们在实际使用时通过package指定别名路径和通过@Alisa注解来指定别名的操作是如何实现的呢?也在TypeAliasRegistry中有实现
/**
* 根据 packagename 来指定
* @param packageName
* @param superType
*/
public void registerAliases(String packageName, Class<?> superType)
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
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);
/**
* 扫描 @Alias注解
* @param type
*/
public void registerAlias(Class<?> type)
String alias = type.getSimpleName();
// 扫描 @Alias注解
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null)
// 获取注解中定义的别名名称
alias = aliasAnnotation.value();
registerAlias(alias, type);
三、TypeHandler的应用
在构建SqlSessionFactory时,在Configuration对象实例化的时候在成员变量中完成了TypeHandlerRegistry和TypeAliasRegistry的实例化
在TypeHandlerRegistry的构造方法中完成了常用类型的TypeHandler的注册
在TypeAliasRegistry中完成了常用Java类型别名的注册
在Configuration的构造方法中会为各种常用的类型向TypeAliasRegistry中注册类型别名数据
以上步骤完成了TypeHandlerRegistry和TypeAliasRegistry的初始化操作。然后在解析全局配置文件时会通过解析<typeAliases>标签和<typeHandlers>标签,可以注册我们添加的别名和TypeHandler。
具体解析的两个方法很简单,大家打开源码查看一下就清楚了。
因为我们在全局配置文件中指定了对应的别名,那么我们在映射文件中就可以简写我们的类型了,这样在解析映射文件时,我们同样也是需要做别名的处理的。在XMLStatementBuilder中
这个parameterType就可以是我们定义的别名,然后在 resolveClass中就会做对应的处理
protected <T> Class<? extends T> resolveClass(String alias)
if (alias == null)
return null;
try
return resolveAlias(alias); // 别名处理
catch (Exception e)
throw new BuilderException("Error resolving class. Cause: " + e, e);
protected <T> Class<? extends T> resolveAlias(String alias)
return typeAliasRegistry.resolveAlias(alias); // 根据别名查找真实的类型
四、执行SQL语句
TypeHandler类型处理器使用比较多的地方应该是在给SQL语句中参数绑定值和查询结果和对象中属性映射的地方用到的比较多,
我们首先进入DefaultParameterHandler中看看参数是如何处理的
/**
* 为 SQL 语句中的 ? 占位符 绑定实参
*/
@Override
public void setParameters(PreparedStatement ps)
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 取出SQL中的参数映射列表
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 处理参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
catch (TypeException | SQLException e)
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
然后进入到DefaultResultSetHandler中的getRowValue方法中
然后再进入applyAutomaticMappings方法中查看,根据对应的TypeHandler返回对应类型的值。
MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory
本文主要介绍MyBatis的反射模块是如何实现的。
MyBatis 反射的核心类Reflector,下面我先说明它的构造函数和成员变量。具体方法下面详解。
org.apache.ibatis.reflection.Reflector public class Reflector { private final Class<?> type; //对应的Class 类型 //可读属性的名称集合,可读属性就是存在相应getter 方法的属性,初始值为空数纽 private final String[] readablePropertyNames; //可写属性的名称集合,可写属性就是存在相应setter 方法的属性,初始值为空数纽 private final String[] writeablePropertyNames; //记录了属性相应的setter 方法, key 是属性名称, value 是Invoker 对象,它是对setter 方法对应 private final Map<String, Invoker> setMethods = new HashMap<>(); //记录了属性相应的getter 方法, key 是属性名称, value 是Invoker 对象,它是对setter 方法对应 private final Map<String, Invoker> getMethods = new HashMap<>(); //记录了属性相应的setter 方法的参数值类型, ke y 是属性名称, value 是setter 方法的参数类型 private final Map<String, Class<?>> setTypes = new HashMap<>(); //记录了属性相应的getter 方法的返回位类型, key 是属性名称, value 是getter 方法的返回位类型 private final Map<String, Class<?>> getTypes = new HashMap<>(); //记录了默认构造方法 private Constructor<?> defaultConstructor; //记录了所有属性名称的集合 private Map<String, String> caseInsensitivePropertyMap = new HashMap<>(); public Reflector(Class<?> clazz) { type = clazz; //查找clazz的无参构造方法,通过反射遍历所有构造方法,找到构造参数集合长度为0的。 addDefaultConstructor(clazz); //处理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合 addGetMethods(clazz); //处理clazz 中的set ter 方法,填充setMethods 集合和set Types 集合 addSetMethods(clazz); //处理没有get/set的方法字段 addFields(clazz); //初始化可读写的名称集合 readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]); writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]); //初始化caseInsensitivePropertyMap ,记录了所有大写格式的属性名称 for (String propName : readablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } for (String propName : writeablePropertyNames) { caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); } } 。。。。。。。。。。。。具体代码先忽略,通过构造函数的调用慢慢渗透。 }
1:addDefaultConstructor() // 查找clazz的无参构造方法,通过反射遍历所有构造方法,找到构造参数集合长度为0的。
主要实现的思想是,通过clazz.getDeclaredConstructors();获取所有构造方法集合,然后循环遍历 判断参数长度为0的,并且构造函数权限可控制的设为默认构造方法。
private void addDefaultConstructor(Class<?> clazz) { Constructor<?>[] consts = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : consts) { if (constructor.getParameterTypes().length == 0) { //判断反射对象的控制权限 为true是可控制 if (canControlMemberAccessible()) { try { //设置Accessible为true后,反射可以访问私有变量。 constructor.setAccessible(true); } catch (Exception e) { // Ignored. This is only a final precaution, nothing we can do. } } if (constructor.isAccessible()) { this.defaultConstructor = constructor; } } } }
2:addGetMethods(clazz)// 处理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合
private void addGetMethods(Class<?> cls) { Map<String, List<Method>> conflictingGetters = new HashMap<>(); //获取当前类以及父类中定义的所有方法的唯一签名以及相应的Method对象。 Method[] methods = getClassMethods(cls); for (Method method : methods) { if (method.getParameterTypes().length > 0) { continue; } String name = method.getName(); //判断如果方法明是以get开头并且方法名长度大于3 或者 方法名是以is开头并且长度大于2 if ((name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2)) { //将方法名截取,如果是is从第二位截取,如果是get或者set从第三位开始截取 name = PropertyNamer.methodToProperty(name); //addMethodConflict 方法内部很简单只有两行代码: //1:List<Method> list=conflictingGetters.computeIfAbsent(name,K->new ArrayList<>()); 这句话的意思是,在conflictingGetters 的Map中 如果key中存在name,name什么都不做,将value返回,如果name不存在,则返回一个新的ArrayList. //2:list.add(method); 将方法对象添加到list对象中。 addMethodConflict(conflictingGetters, name, method); } } resolveGetterConflicts(conflictingGetters); }
2-1:getClassMethods(cls);//获取当前类以及父类中定义的所有方法的唯一签名以及相应的Method对象。
private Method[] getClassMethods(Class<?> cls) { Map<String, Method> uniqueMethods = new HashMap<>(); Class<?> currentClass = cls; while (currentClass != null && currentClass != Object.class) { //currentClass.getDeclaredMethods(),获取当前类的所有方法 //addUniqueMethods 为每个方法生成唯一签名,并记录到uniqueMethods集合中 addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); // we also need to look for interface methods - // because the class may be abstract Class<?>[] interfaces = currentClass.getInterfaces(); for (Class<?> anInterface : interfaces) { addUniqueMethods(uniqueMethods, anInterface.getMethods()); } currentClass = currentClass.getSuperclass(); } Collection<Method> methods = uniqueMethods.values(); return methods.toArray(new Method[methods.size()]); }
2-1-1: addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); 为每个方法生成唯一签名,并记录到uniqueMethods集合中
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) { for (Method currentMethod : methods) { //判断是不是桥接方法, 桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法 if (!currentMethod.isBridge()) { //获取签名 // 签名格式为:方法返回参数#方法名:参数名 ps:多个参数用,分割 签名样例:String#getName:User String signature = getSignature(currentMethod); // check to see if the method is already known // if it is known, then an extended class must have // overridden a method //如果签名存在,则不做处理,表示子类已经覆盖了该方法。 //如果签名不存在,则将签名作为Key,Method作为value 添加到uniqueMethods中 if (!uniqueMethods.containsKey(signature)) { if (canControlMemberAccessible()) { try { currentMethod.setAccessible(true); } catch (Exception e) { // Ignored. This is only a final precaution, nothing we can do. } } uniqueMethods.put(signature, currentMethod); } } } }
2-2: resolveGetterConflicts(conflictingGetters);;//在2-1中返回的方法可能存在,两个相同的方法名称,因为当子类实现父类方法时且参数不同,此时2-1生成的签名是不同的生成签名的规则是 方法返回值#方法名#参数名,那么就会返回两个相同的方法名。 resolveGetterConflicts方法会对这种覆写的情况进行处理,同时将处理后的getter方法记录到getMethods集合中,将其返回值类型填充到getTypes集合中。 内部实现主要是两个for循环,循环比较方法名称相同的情况下,返回值不同的情况下,拿第二个当最终想要的Method。
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) { for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) { Method winner = null; String propName = entry.getKey(); for (Method candidate : entry.getValue()) { if (winner == null) { winner = candidate; continue; } Class<?> winnerType = winner.getReturnType(); Class<?> candidateType = candidate.getReturnType(); if (candidateType.equals(winnerType)) { if (!boolean.class.equals(candidateType)) { throw new ReflectionException( "Illegal overloaded getter method with ambiguous type for property " + propName + " in class " + winner.getDeclaringClass() + ". This breaks the JavaBeans specification and can cause unpredictable results."); } else if (candidate.getName().startsWith("is")) { winner = candidate; } } else if (candidateType.isAssignableFrom(winnerType)) { // OK getter type is descendant } else if (winnerType.isAssignableFrom(candidateType)) { winner = candidate; } else { throw new ReflectionException( "Illegal overloaded getter method with ambiguous type for property " + propName + " in class " + winner.getDeclaringClass() + ". This breaks the JavaBeans specification and can cause unpredictable results."); } } addGetMethod(propName, winner); } }
总结一下addGetMethods(clazz)方法和addSetMethods(clazz)大致相同:
首先创建:
Map<String, List<Method>> conflictingGetters = new HashMap<>();
1:获取子类和父类的所有方法。 获取方法是:先生成唯一签名,唯一签名规则是方法返回值#方法名:方法参数1,方法参数2 。 根据签名作为key,method对象作为value生成Map,通过签名进行过滤,将此Map转换为List返回。
2:循环遍历Map,找到符合条件的方法名,is开头或者get开头的,将方法名截取,截取后的方法名作为key,List<Method>作为value,放入到conflictingGetters中。
3:由于子类存在实现父类方法,且返回值不同的情况,导致用一方法名可能有不同的Method ,第三步 resolveGetterConflicts方法会对这种覆写的情况进行处理,同时将处理后的getter方法记录到getMethods集合中,将其返回值类型填充到getTypes集合中。
Reflector Factory 接口主要实现了对Reflector对象的创建和缓存,有三个方法:该接口定义如下:
public interface ReflectorFactory { boolean isClassCacheEnabled(); //检测该ReflectorFactory对象是否会缓存Reflector对象 void setClassCacheEnabled(boolean classCacheEnabled);//设置是否缓存Reflector对象 Reflector findForClass(Class<?> type); //创建指定class对应的Reflector对象 }
Reflector Factory的实现是DefaultReflectorFactory,具体实现如下:
public class DefaultReflectorFactory implements ReflectorFactory { private boolean classCacheEnabled = true; private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>(); public DefaultReflectorFactory() { } @Override public boolean isClassCacheEnabled() { return classCacheEnabled; } @Override public void setClassCacheEnabled(boolean classCacheEnabled) { this.classCacheEnabled = classCacheEnabled; } @Override public Reflector findForClass(Class<?> type) { //如果开启缓存,Reflector对象从ConcurrentMap<Class<?>, Reflector> 取出。 if (classCacheEnabled) { // synchronized (type) removed see issue #461 return reflectorMap.computeIfAbsent(type, Reflector::new); } else {//没开启缓存,重新创建。 return new Reflector(type); } } }
DefaultReflectorFactory 的缓存是通过ConcurrentMap来实现的,如果开启了缓存,那么就从ConcurrentMap取Reflector,如果没有开启,就新建Reflector.
除了使用MyBatis提供的DefaultReflectorFactory实现,我们还可以在mybatis-config.xml中配置自定义的ReflectorFactory 实现类,从而实现功能上的扩展。
以上是关于MyBatis源码阅读: MyBatis基础模块-类型转换模块的主要内容,如果未能解决你的问题,请参考以下文章
MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory
浩哥解析MyBatis源码——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)