之前我们知道mybatis的代码里面很多包都是提供工具类的支持,其主要分布在
其中被加粗的这几个模块中要么和核心业务逻辑关联不大,要么是一些很公共的方法,我们在其他的源码或日常工作中多有了解,所以我们除了一些重点和特殊的逻辑,一般只介绍一些包结构以及具体实现业务,其内部逻辑可以自行阅读。
cache类是mybatis缓存的顶级接口,
public interface Cache {
/**
* @return The identifier of this cache
* 获得缓存的标识
*/
String getId();
/**
* 保存数据
*/
void putObject(Object key, Object value);
/**
* 查询数据
*/
Object getObject(Object key);
/**
* 移除数据
*/
Object removeObject(Object key);
/**
* 清空缓存
*/
void clear();
/**
*/
int getSize();
/**
* 获取读写锁
* @return A ReadWriteLock
*/
ReadWriteLock getReadWriteLock();
}
TransactionalCacheManager是其事务缓存的管理器
public class TransactionalCacheManager {
// Cache 和 TransactionalCache 的映射
private final Map transactionalCaches = new HashMap<>();
// 清空
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
// 取值
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
// 存放
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
// 提交,循环值获得事务,执行提交
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
// 回滚,循环值获得事务,执行回滚
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
剩余的是缓存个各种实现类
数据源工厂接口
public interface DataSourceFactory {
// 设置数据源
void setProperties(Properties props);
// 获得数据源
DataSource getDataSource();
}
然后两种数据源工厂实现,对应两种数据源
一般来说数据源的处理我们都是使用其他依赖来进行处理,使用mybatis的机会不多,里面逻辑可以自行了解
关于池化的数据源工厂中的逻辑
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
而其实际上创建的是非池化的数据源
public PooledDataSource() {
dataSource = new UnpooledDataSource();
}
其内部会记录数据源的状态和维持数据源集合
// 记录状态
private final PoolState state = new PoolState(this);
/**
* 连接池状态,记录空闲和激活的 PooledConnection 集合
* @author Clinton Begin
*/
public class PoolState {
// 数据源对象
protected PooledDataSource dataSource;
// 空闲的数据源
protected final List idleConnections = new ArrayList<>();
// 激活的数据源
protected final List activeConnections = new ArrayList<>();
// 获取连接的次数
protected long requestCount = 0;
// 获取连接的时间
protected long accumulatedRequestTime = 0;
// 获取到连接非超时 + 超时的占用时长
protected long accumulatedCheckoutTime = 0;
// 获取到连接超时的次数
protected long claimedOverdueConnectionCount = 0;
// 获取到连接超时的占用时长
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
// 等待连接的时间
protected long accumulatedWaitTime = 0;
// 等待连接的次数
protected long hadToWaitCount = 0;
// 获取到坏的连接的次数
protected long badConnectionCount = 0;
//.......
}
目前mybatis没有对异常做出什么拓展和其他不同寻常的处理,主要异常类型被分布在各个包中
reflection
包:ReflectionExceptionlogging
包:LogExceptionbuilder
包:BuilderException、IncompleteElementExceptionscripting
包:ScriptingExceptionbinding
包:BindingExceptiontype
包:TypeExceptionsession
包:SqlSessionExceptioncache
包:CacheExceptiontransaction
包:TransactionExceptiondatasource
包:DataSourceExceptionexecutor
包:ResultMapException、ExecutorException、BatchExecutorExceptionplugin
包:PluginException一个简单的使用原生JDBC进行查询的逻辑
mybatis日志的初始化是在LogFactory中,其逐个尝试可以加载的实现类,直到加载到实现的日志
static {
// 逐个尝试,判断使用哪个 Log 的实现类,
// 即初始化 logConstructor 属性
/*
tryImplementation(LogFactory::useSlf4jLogging) 等价于
tryImplementation(new Runnable() {
@Override
public void run() {
LogFactory.useSlf4jLogging();
}
});*/
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
mybatis在每个实现类下,实现了不同的日志格式,其中jdbc包下实现了基础的日志实现,以供其他类型日志系统使用
主要是对类加载器的一个封装,假如了解java的类加载,双亲模式,会简单很多
/**
* A class to wrap access to multiple class loaders making them work as one
* ClassLoader 包装器。可使用多个 ClassLoader 加载对应的资源,直到有一成功后返回资源。
* @author Clinton Begin
*/
public class ClassLoaderWrapper {
// 默认的类加载器
ClassLoader defaultClassLoader;
// 系统的类加载器
ClassLoader systemClassLoader;
//.......
}
其他几个文件
DefaultVFS:继承 VFS 抽象类,默认的 VFS 实现类
JBoss6VFS:继承 VFS 抽象类,默认的 VFS 实现类(JBoss版本)
ResolverUtil:解析器工具类Resources:Resource 工具类
VFS:虚拟文件系统( Virtual File System )抽象类,用来查找指定路径下的的文件们
反射是一个很强大的功能,而且实际开发中也经常用到,反射的方法其实都不难理解,但是想写的好也是要非一番功夫的。(一般企业中应该都有封装好的各种反射工具)。
而mybatis自己写了一些反射工具,用来方便自己实现相关业务逻辑。
首先,反射模块中很重要的一个类就是反射器类Reflector。
public class Reflector {
// 对应的类
private final Class> type;
// 可读属性数组
private final String[] readablePropertyNames;
// 可写属性集合
private final String[] writablePropertyNames;
// 属性对应的 setting 方法的映射。
// key 为属性名称 value 为 Invoker 对象
private final Map setMethods = new HashMap<>();
// 属性对应的 getting 方法的映射。
// key 为属性名称 value 为 Invoker 对象
private final Map getMethods = new HashMap<>();
// 属性对应的 getting 方法的方法参数类型的映射。{@link #setMethods}
// key 为属性名称 value 为 参数类型 对象
private final Map> setTypes = new HashMap<>();
// 属性对应的 setting 方法的方法参数类型的映射。{@link #setMethods}
// key 为属性名称 value 为 参数类型 对象
private final Map> getTypes = new HashMap<>();
// 默认构造器
private Constructor> defaultConstructor;
// 不区分大小写的属性集合
private Map caseInsensitivePropertyMap = new HashMap<>();
// 每一个类对应一个反射器
public Reflector(Class> clazz) {
// 设置对应的类
type = clazz;
// 初始化 defaultConstructor
addDefaultConstructor(clazz);
// 初始化 getMethods 和 getTypes ,通过遍历 getting 方法
addGetMethods(clazz);
// 初始化 setMethods 和 setTypes ,通过遍历 setting 方法。
addSetMethods(clazz);
// 初始化 getMethods + getTypes 和 setMethods + setTypes ,通过遍历 fields 属性。
addFields(clazz);
// readablePropertyNames、writeablePropertyNames、caseInsensitivePropertyMap 属性
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
writablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
// 获得默认构造方法
private void addDefaultConstructor(Class> clazz) {
// 获得所有构造方法
Constructor>[] consts = clazz.getDeclaredConstructors();
for (Constructor> constructor : consts) {
// 获得参数为0的构造方法
if (constructor.getParameterTypes().length == 0) {
this.defaultConstructor = constructor;
}
}
}
private void addGetMethods(Class> cls) {
// 创建缓存map
Map> conflictingGetters = new HashMap<>();
// 获得所有get方法,并且循环遍历
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
// 以get和is开头的认为是get方法
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingGetters, name, method);
}
}
// 因为get方法子类和父类可能同时存在所以进行筛选
resolveGetterConflicts(conflictingGetters);
}
private void resolveGetterConflicts(Map> conflictingGetters) {
// 遍历每个属性,查找其最匹配的方法。因为子类可以覆写父类的方法,所以一个属性,可能对应多个 getting 方法
for (Entry> entry : conflictingGetters.entrySet()) {
// 最匹配的方法
Method winner = null;
String propName = entry.getKey();
for (Method candidate : entry.getValue()) {
// winner 为空,说明 candidate 为最匹配的方法
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
// 符合选择子类。因为子类可以修改放大返回值。例如,
// 父类的一个方法的返回值为 List ,子类对该方法的返回值可以覆写为 ArrayList
} 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);
}
}
private void addGetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
getMethods.put(name, new MethodInvoker(method));
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
getTypes.put(name, typeToClass(returnType));
}
}
// 主要逻辑和addGetMethods类似
private void addSetMethods(Class> cls) {
// 创建映射缓存
Map> conflictingSetters = new HashMap<>();
// 获得所有方法,并且循环获得set数据
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("set") && name.length() > 3) {
if (method.getParameterTypes().length == 1) {
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingSetters, name, method);
}
}
}
// 处理冲突
resolveSetterConflicts(conflictingSetters);
}
private void addMethodConflict(Map> conflictingMethods, String name, Method method) {
List list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>());
list.add(method);
}
// 实际和 getting 方法的方式是不太一样的。首先,多的就是考虑了对应的 getterType 为优先级最高
private void resolveSetterConflicts(Map> conflictingSetters) {
// 遍历每个属性,查找其最匹配的方法。因为子类可以覆写父类的方法,所以一个属性,可能对应多个 setting 方法
for (String propName : conflictingSetters.keySet()) {
List setters = conflictingSetters.get(propName);
Class> getterType = getTypes.get(propName);
Method match = null;
ReflectionException exception = null;
// 遍历属性对应的 setting 方法
for (Method setter : setters) {
Class> paramType = setter.getParameterTypes()[0];
// 和 getterType 相同,直接使用
if (paramType.equals(getterType)) {
// should be the best match
match = setter;
break;
}
if (exception == null) {
try {
// 选择一个更加匹配的
match = pickBetterSetter(match, setter, propName);
} catch (ReflectionException e) {
// there could still be the 'best match'
match = null;
exception = e;
}
}
}
if (match == null) {
throw exception;
} else {
addSetMethod(propName, match);
}
}
}
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
if (setter1 == null) {
return setter2;
}
Class> paramType1 = setter1.getParameterTypes()[0];
Class> paramType2 = setter2.getParameterTypes()[0];
if (paramType1.isAssignableFrom(paramType2)) {
return setter2;
} else if (paramType2.isAssignableFrom(paramType1)) {
return setter1;
}
throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
+ setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
+ paramType2.getName() + "'.");
}
private void addSetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
setMethods.put(name, new MethodInvoker(method));
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
setTypes.put(name, typeToClass(paramTypes[0]));
}
}
private Class> typeToClass(Type src) {
Class> result = null;
if (src instanceof Class) {
result = (Class>) src;
} else if (src instanceof ParameterizedType) {
result = (Class>) ((ParameterizedType) src).getRawType();
} else if (src instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) src).getGenericComponentType();
if (componentType instanceof Class) {
result = Array.newInstance((Class>) componentType, 0).getClass();
} else {
Class> componentClass = typeToClass(componentType);
result = Array.newInstance(componentClass, 0).getClass();
}
}
if (result == null) {
result = Object.class;
}
return result;
}
// 它是 #addGetMethods(...) 和 #addSetMethods(...) 方法的补充,
// 因为有些 field ,不存在对应的 setting 或 getting 方法,所以直接使用对应的 field
private void addFields(Class> clazz) {
// 获得所有field
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 设置 字段可以访问
if (!setMethods.containsKey(field.getName())) {
// issue #379 - removed the check for final because JDK 1.5 allows
// modification of final fields through reflection (JSR-133). (JGB)
// pr #16 - final static can only be set by the classloader
int modifiers = field.getModifiers();
// 添加到 setMethods 和 setTypes 中
if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
addSetField(field);
}
}
if (!getMethods.containsKey(field.getName())) {
addGetField(field);
}
}
// 递归处理父类
if (clazz.getSuperclass() != null) {
addFields(clazz.getSuperclass());
}
}
private void addSetField(Field field) {
if (isValidPropertyName(field.getName())) {
setMethods.put(field.getName(), new SetFieldInvoker(field));
Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
setTypes.put(field.getName(), typeToClass(fieldType));
}
}
private void addGetField(Field field) {
if (isValidPropertyName(field.getName())) {
getMethods.put(field.getName(), new GetFieldInvoker(field));
Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
getTypes.put(field.getName(), typeToClass(fieldType));
}
}
private boolean isValidPropertyName(String name) {
return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name));
}
/**
* This method returns an array containing all methods
* declared in this class and any superclass.
* We use this method, instead of the simpler Class.getMethods()
,
* because we want to look for private methods as well.
*
* @param cls The class
* @return An array containing all methods in this class
*/
private Method[] getClassMethods(Class> cls) {
// 每个方法签名与该方法的映射
Map uniqueMethods = new HashMap<>();
// 循环类,类的父类,类的父类的父类,直到父类为 Object
Class> currentClass = cls;
while (currentClass != null && currentClass != Object.class) {
// 记录当前类定义的方法
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 methods = uniqueMethods.values();
return methods.toArray(new Method[methods.size()]);
}
private void addUniqueMethods(Map uniqueMethods, Method[] methods) {
// 忽略 bridge 方法,参见 https://www.zhihu.com/question/54895701/answer/141623158
// TODO
for (Method currentMethod : methods) {
// 获得方法签名
if (!currentMethod.isBridge()) {
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
// 当缓存中不存在的时候才进行新增
if (!uniqueMethods.containsKey(signature)) {
uniqueMethods.put(signature, currentMethod);
}
}
}
}
private String getSignature(Method method) {
StringBuilder sb = new StringBuilder();
Class> returnType = method.getReturnType();
if (returnType != null) {
sb.append(returnType.getName()).append('#');
}
sb.append(method.getName());
Class>[] parameters = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
if (i == 0) {
sb.append(':');
} else {
sb.append(',');
}
sb.append(parameters[i].getName());
}
return sb.toString();
}
/**
* Checks whether can control member accessible.
*
* @return If can control member accessible, it return {@literal true}
* @since 3.5.0
*/
public static boolean canControlMemberAccessible() {
try {
SecurityManager securityManager = System.getSecurityManager();
if (null != securityManager) {
securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
}
} catch (SecurityException e) {
return false;
}
return true;
}
/**
* Gets the name of the class the instance provides information for.
*
* @return The class name
*/
public Class> getType() {
return type;
}
public Constructor> getDefaultConstructor() {
if (defaultConstructor != null) {
return defaultConstructor;
} else {
throw new ReflectionException("There is no default constructor for " + type);
}
}
public boolean hasDefaultConstructor() {
return defaultConstructor != null;
}
public Invoker getSetInvoker(String propertyName) {
Invoker method = setMethods.get(propertyName);
if (method == null) {
throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
}
return method;
}
public Invoker getGetInvoker(String propertyName) {
Invoker method = getMethods.get(propertyName);
if (method == null) {
throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
}
return method;
}
/**
* Gets the type for a property setter.
*
* @param propertyName - the name of the property
* @return The Class of the property setter
*/
public Class> getSetterType(String propertyName) {
Class> clazz = setTypes.get(propertyName);
if (clazz == null) {
throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
}
return clazz;
}
/**
* Gets the type for a property getter.
* 返回属性的类型参数
* @param propertyName - the name of the property
* @return The Class of the property getter
*/
public Class> getGetterType(String propertyName) {
Class> clazz = getTypes.get(propertyName);
if (clazz == null) {
throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
}
return clazz;
}
/**
* Gets an array of the readable properties for an object.
*
* @return The array
*/
public String[] getGetablePropertyNames() {
return readablePropertyNames;
}
/**
* Gets an array of the writable properties for an object.
*
* @return The array
*/
public String[] getSetablePropertyNames() {
return writablePropertyNames;
}
/**
* Check to see if a class has a writable property by name.
*
* @param propertyName - the name of the property to check
* @return True if the object has a writable property by the name
*/
public boolean hasSetter(String propertyName) {
return setMethods.keySet().contains(propertyName);
}
/**
* Check to see if a class has a readable property by name.
*
* @param propertyName - the name of the property to check
* @return True if the object has a readable property by the name
*/
public boolean hasGetter(String propertyName) {
return getMethods.keySet().contains(propertyName);
}
public String findPropertyName(String name) {
return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
}
}
每个 Reflector 对应一个类。Reflector 会缓存反射操作需要的类的信息,例如:构造方法、属性名、setting / getting 方法等等。
而创建反射器的就是通过反射器工厂Reflector
/**
* Reflector 工厂接口,用于创建和缓存 Reflector 对象
*/
public interface ReflectorFactory {
/**
* 是否缓存 Reflector 对象
* @return
*/
boolean isClassCacheEnabled();
/**
* 设置是否缓存 Reflector 对象
* @param classCacheEnabled
*/
void setClassCacheEnabled(boolean classCacheEnabled);
/**
* 获取 Reflector 对象
* @param type
* @return
*/
Reflector findForClass(Class> type);
}
DefaultReflectorFactory是其默认实现类。
对于方法的调用,其接口为
/**
* 调用者接口
* @author Clinton Begin
*/
public interface Invoker {
// 执行调用
Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
// 返回类
Class> getType();
}
GetFieldInvoker,MethodInvoker,SetFieldInvoker 是其主要实现类
而ObjectWrapperFactory是其类创建工厂,其最终创建出来的是objectWrapper而不是object
/**
* ObjectWrapper 工厂接口
* @author Clinton Begin
*/
public interface ObjectWrapperFactory {
boolean hasWrapperFor(Object object);
ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);
}
/**
* 对象包装类
* @author Clinton Begin
*/
public interface ObjectWrapper {
// 获得对象 值
// PropertyTokenizer 对象,相当于键
Object get(PropertyTokenizer prop);
// 设置
// @param prop PropertyTokenizer 对象,相当于键
void set(PropertyTokenizer prop, Object value);
// 查询参数
String findProperty(String name, boolean useCamelCaseMapping);
// 获得get参数名称
String[] getGetterNames();
// 获得set参数名称
String[] getSetterNames();
// 获得set参数类型
Class> getSetterType(String name);
// 获得get参数类型
Class> getGetterType(String name);
// 是否为set方法
boolean hasSetter(String name);
// 是否为get方法
boolean hasGetter(String name);
// 设置值
MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);
// 是否集合
boolean isCollection();
// 添加元素到集合
void add(Object element);
// 添加多个元素到集合
void addAll(List element);
}
而Property包下的三个文件就是其对属性的处理了
剩下的几个类是
类型转换大部分类都是其顶级接口的实现类
/**
* 类型转换的处理器的顶层接口
* @author Clinton Begin
*/
public interface TypeHandler {
/**
* 设置PreparedStatement的指定参数
* @param ps PreparedStatement对象
* @param i 参数占位符的位置
* @param parameter parameter 参数
* @param jdbcType jdbcType jdbc类型
* Java Type => JDBC Type
* @throws SQLException
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 获得 返回的指定字段的值
* @param rs ResultSet对象
* @param columnName 字段名称
* JDBC Type => Java Type
* @return
* @throws SQLException
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* 获得返回的值指定索引的值
* @param rs ResultSet对象
* @param columnIndex 字段索引
* @return
* @throws SQLException
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
/**
* 获得 CallableStatement 的指定字段的值
* @param cs CallableStatement 对象,支持调用存储过程
* @param columnIndex 字段位置
* @return
* @throws SQLException
*/
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
其子类从名字上就可以看出来,命名规则为{类型名}TypeHandler的组合。
其他重要的内容:
另外几个以Registry结尾的类,注册了很多数据
ps.目前简单的基础支持模块的逻辑大概就这么多,后续是针对较为复杂或者和业务有关联的基础逻辑的笔记整理
目前学习中的注释都可以在: 中https://gitee.com/daifylearn/mybatis-3查看