整体架构
MyBatis 的整体架构分为三层, 分别是基础支持层、核心处理层和接口层。
基础支持层
基础支持层包含整个MyBatis 的基础模块,这些模块为核心处理层的功能提供了良好的支撑。
解析器模块
XPathParser
MyBatis提供的XPathParser
类封装了XPath
、Document
和EntityResolver
。
XPathParser
中提供了一系列的eval*()
方法用于解析boolean 、short、long 、int 、String 、Node
等类型的信息,它通过调用前面介绍的XPath.evaluate()
方法查找指定路径的节点或属性,并进行相应的类型装换。
XPathParser.evalString()
方法,其中会调用PropertyParser.parse()
方法处理节点中相应的默认值。
在PropertyParser
中指定了是否开启使用默认值的功能以及默认的分隔符。
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";
private static final String ENABLE_DEFAULT_VALUE = "false";
private static final String DEFAULT_VALUE_SEPARATOR = ":";
PropertyParser.parse()
方法中会创建GenericTokenParser
解析器,井将默认值的处理委托给GenericTokenParser.parse()
方法。
GenericTokenParser
是一个通用的宇占位符解析器。GenericTokenParser.parse
方法的会顺序查找openToken
和closeToken
,解析得到占位符的字面值,并将其交给TokenHandler
处理, 然后将解析结果重新拼装成字符串井返回。
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
}
占位符由TokenHandler
接口的实现进行解析, TokenHandler
接口总共有四个实现:
- BindingTokenParser
- DynamicCheckerTokenParser
- ParameterMappingTokenHandler
- VariableTokenHandler
PropertyParser
是使用VariableTokenHandler
与GenericTokenParser
配合完成占位符解析的。
VariableTokenHandler
实现了TokenHandler
接口中的handleToken
方法,该实现首先会按照defaultValueSeparator
宇段指定的分隔符对整个占位符切分, 得到占位符的名称和默认值,然后按照切分得到的占位符名称查找对应的值, 如果在<properties>
节点下未定义相应的键值对,则将切分得到的默认值作为解析结果返回。
反射工具箱
为了简化反射操作的相关代码, MyBatis提供了专门的反射模块,该模块位于org.apache.ibatis.reflection
包中,它对常见的反射操作做了进一步封装,提供了更加简洁方便的反射API。
Reflector & ReflectorFactory
Reflector
是MyBatis 中反射模块的基础,每个Reflector
对象都对应一个类,在Reflector
中缓存了反射操作需要使用的类的元信息。
public class Reflector {
private final Class<?> type; //class
private final String[] readablePropertyNames; //可读属性,getter
private final String[] writablePropertyNames; //可写属性,setter。
//key:属性,value:invoker对象
private final Map<String, Invoker> setMethods = new HashMap<>();
//key:属性,value:invoker对象
private final Map<String, Invoker> getMethods = new HashMap<>();
//setter方法的参数值类型,key:属性,value:参数值类型列表。
private final Map<String, Class<?>> setTypes = new HashMap<>();
private final Map<String, Class<?>> getTypes = new HashMap<>();
private Constructor<?> defaultConstructor;
//所有属性
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
}
TypeParameterResolver
Type 是所有类型的父接口,它有四个子接口和一个实现类。
TypeParameterResolver
中通过resolveFieldType
方法、resolveReturnType
方法、
resolveParamTypes
方法分别解析字段类型、方法返回值类型和方法参数列表中各个参数的类型。
ObjectFactory
MyBatis 中有很多模块会使用到ObjectFactory
接口,该接口提供了多个create
方法的重载,通过这些create
方法可以创建指定类型的对象。
public interface ObjectFactory {
/** 设置配置信息*/
default void setProperties(Properties properties) {
// NOP
}
/** 通过无参构造函数创建对象*/
<T> T create(Class<T> type);
/** 通过参数类型,选择合适的构造函数创建对象*/
<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
/** 检查是否是集合类型*/
<T> boolean isCollection(Class<T> type);
}
DefaultObjectFactory
是MyBatis 提供的ObjectFactory
接口的唯一实现,它是一个反射工厂,其create
方法通过调用instantiateClass()
方法实现。DefaultObjectFactory.instantiateClass
方法会根据传入的参数列表选择合适的构造函数实例化对象.
Property 工具集
反射模块中使用到的三个属性工具类,分别是PropertyTokenizer
、PropertyNamer
、PropertyCopier
。
“ orders[0].items[0].name
”这种由“ .
”和“ []
”组成的表达式是由PropertyTokenizer
进行解析的。
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
private String name;
private final String indexedName;
private String index;
private final String children;
public PropertyTokenizer(String fullname) {
}
@Override
public PropertyTokenizer next() {
return new PropertyTokenizer(children);
}
}
PropertyTokenizer
的构造方法中会对传入的表达式进行分析,并初始化上述字段。
PropertyTokenizer
继承了Iterator 接口,它可以法代处理嵌套多层表达式。PropertyTokenizer.next()
方法中会创建新的PropertyTokenizer
对象并解析children 宇段记录的子表达式。
orders[0].items[0].name
的迭代过程如下:
PropertyNamer
是另一个工具类,提供了静态方法帮助完成方法名到属性名的转换,以及多种检测操作。
public static String methodToProperty(String name) {
if (name.startsWith("is")) {
name = name.substring(2);
} else if (name.startsWith("get") || name.startsWith("set")) {
name = name.substring(3);
} else {
throw new ReflectionException("Error parsing property name '" + name + "'. Didn't start with 'is', 'get' or 'set'.");
}
if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
}
return name;
}
PropertyCopier
是一个属性拷贝的工具类,其核心方法是copyBeanProperties
方法, 主要实现相同类型的两个对象之间的属性值拷贝。
MetaClass
MetaClass
通过Reflector
和PropertyTokenizer
组合使用, 实现了对复杂的属性表达式的解析,并实现了获取指定属性描述信息的功能。
public class MetaClass {
private final ReflectorFactory reflectorFactory;
private final Reflector reflector;
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
this.reflector = reflectorFactory.findForClass(type);
}
}
MetaClass. findProperty()
方法只查找“.
”导航的属性,并没有检测下标。
User 类中的tele.num
这个属性表达式,最终得到builder
中记录的字符串为tele.num
ObjectWrapper
MetaClass
是MyBatis 对类级别的元信息的封装和处理。ObjectWrapper
接口是对对象的包装,抽象了对象的属性信息,它定义了一系列查询对象属性信息的方法,以及更新属性的方法。
public interface ObjectWrapper {
//如采Object Wrapper 中封装的是普通的Bean 对象,则调用相应属性的相应getter 方法,如采封装的是集合类,则获取指定key 或下标对应的value 位
Object get(PropertyTokenizer prop);
void set(PropertyTokenizer prop, Object value);
String findProperty(String name, boolean useCamelCaseMapping);
String[] getGetterNames();
String[] getSetterNames();
Class<?> getSetterType(String name);
Class<?> getGetterType(String name);
boolean hasSetter(String name);
boolean hasGetter(String name);
MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);
boolean isCollection();
void add(Object element);
<E> void addAll(List<E> element);
}
BaseWrapper
是一个实现了ObjectWrapper
接口的抽象类, 其中封装了MetaObject
对象
public abstract class BaseWrapper implements ObjectWrapper {
protected static final Object[] NO_ARGUMENTS = new Object[0];
protected final MetaObject metaObject;
protected BaseWrapper(MetaObject metaObject) {
this.metaObject = metaObject;
}
//调用MetaObject.getValue()方法,它会解析属性表达式井获取指定的属性
protected Object resolveCollection(PropertyTokenizer prop, Object object) {
if ("".equals(prop.getName())) {
return object;
} else {
return metaObject.getValue(prop.getName());
}
}
//
protected Object getCollectionValue(PropertyTokenizer prop, Object collection) {
}
//
protected void setCollectionValue(PropertyTokenizer prop, Object collection, Object value) {
}
}
BeanWrapper
继承了BaseWrapper
抽象类,其中封装了一个JavaBean
对象以及该JavaBean
类相应的MetaClass
对象,当然,还有从BaseWrapper
继承下来的、该JavaBean 对象相应的MetaObject
对象。
public class BeanWrapper extends BaseWrapper {
private final Object object;
private final MetaClass metaClass;
public BeanWrapper(MetaObject metaObject, Object object) {
super(metaObject);
this.object = object;
this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
}
}
CollectionWrapper
实现了ObjectWrapper
接口,其中封装了Collection<Object>
类型的对象。
MapWrapper
是BaseWrapper
的另一个实现类,其中封装的是Map<String, Object>
类型对象。
MetaObject
ObjectWrapper
提供了获取/设置对象中指定的属性值、检测getter/setter
等常用功能,但是ObjectWrapper
省略了对属性表达式解析过程的介绍,而该解析过程是在MetaObject
中实现的。
public class MetaObject {
//Java 原始bean对象
private final Object originalObject;
//封装了 originalObject
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
//
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
//封装原始对象。
this.objectWrapper = new BeanWrapper(this, object);
}
}
}
MetaObject
和ObjectWrapper
中关于类级别的方法,例如hasGetter()
、hasSetter()
、findProperty()
等方法,都是直接调用MetaClass
的对应方法实现的。其他方法都是关于对象级别的方法,这些方法都是与ObjectWrapper
配合实现。
类型转换
JDBC 数据类型与Java 语言中的数据类型并不是完全对应的,所以在PreparedStatement
为SQL 语句绑定参数时,需要从Java 类型转换成JDBC 类型,而从结果集中获取数据时,则需要从JDBC 类型转换成Java 类型。My Batis 使用类型处理器(TypeHanlder)完成上述两种转换。
在MyBatis 中使用JdbcType
这个枚举类型代表JDBC 中的数据类型,该枚举类型中定义了TYPE_CODE
字段,记录了JDBC 类型在java.sql.Types
中相应的常量编码,并通过一个静态集合codeLookup
( HashMap<Integer,JdbcType>
类型〉维护了常量编码与JdbcType
之间的对应关系。
TypeHandler
MyBatis 中所有的类型转换器都继承了TypeHandler
接口。
public interface TypeHandler<T> {
//在通过PreparedStatement 为SQL 语句绑定参数时,会将数据由JdbcType 类型转换成Java类型
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//从ResultSet 中获取数据时会调用此方法,会将数据由Java 类型转换成JdbcType 类型
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
MyBatis 提供了BaseTypeHandler
这个抽象类,它实现了TypeHandler
接口,并继承了TypeReference
抽象类。
在BaseTypeHandler
中实现了TypeHandler.setParameter ()
方法和TypeHandler.getResult()
方法,这两个方法对于非空数据的处理都交给了子类实现。
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
@Deprecated
protected Configuration configuration;
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
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);
}
}
@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);
}
}
@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;
/**
* @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
*/
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;
}
BaseTypeHandler
的实现类比较多,但大多是直接调用PreparedStatement
和ResultSet
或CallableStatement
的对应方法,实现比较简单。
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 初始化过程中,会为所有己知的TypeHandler
创建对象,并实现注册到TypeHandlerRegistry
中,由TypeHandlerRegister
负责管理这些TypeHandler
对象。
public final class TypeHandlerRegistry {
//记录JdbcType 与TypeHandler 之间的对应关系,其中JdbcType 是一个枚举类型,它定义对应的JDBC 类型
//该集合主要用于从结果集读取数据时,将数据从Jdbc 类型转换成Java 类型
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
//记录了Java 类型向指定JdbcType 转换时,需妥使用的TypeHandler 对象。例如: Java 类型中的String 可能转换成数据库的char 、varchar 等多种类型,所以存在一对多关系
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler = new UnknownTypeHandler(this);
//记录了全部TypeHandler 的类型以及该类型相应的T ypeHandler 对象
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
//空TypeHandler 集合的标识
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
//注册 TypeHandler,根据 @MappedTypes
public <T> void register(TypeHandler<T> typeHandler){}
//
public void register(String packageName) {}
//
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;
}
}
注册TypeHandler 对象
TypeHandlerRegistry.register()
方法实现了注册TypeHandler
对象的功能。
register ()
方法重载中会尝试读取TypeHandler
类中定义的@MappedTypes
注解和@MappedJdbcTypes
注解,@MappedTypes
注解用于指明该TypeHandler
实现类能够处理的Java 类型的集合,@MappedJdbcTypes
注解用于指明该TypeHandler
实现类能够处理的JDBC类型集合。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedTypes {
Class<?>[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedJdbcTypes {
JdbcType[] value();
boolean includeNullJdbcType() default false;
}
TypeHandlerRegister
除了提供注册单个TypeHandler
的register()
重载,还可以扫描整个包下的TypeHandler
接口实现类,并将完成这些TypeHandler
实现类的注册。
查找TypeHandler
TypeAliasRegistry
MyBatis 将SQL 语句中别名的概念进行了延伸和扩展, MyBatis可以为一个类添加一个别名,之后就可以通过别名引用该类。
MyBatis 通过TypeAliasRegister
类完成别名注册和管理的功能, TypeAliasRegistry
的结构比较简单,它通过TYPE_ALIASES 字段(Map<String, Class <?>>
类型)管理别名与Java 类型之间的对应关系,通过TypeAliasRegistry.registerAlias()
方法完成注册别名.
在TypeAliasRegistry
的构造方法中,默认为Java 的基本类型及其数组类型、基本类型的封装类及其数组类型、Date 、BigDecimal
、Biglnteger
、Map
、HashMap
、List 、ArrayList、Collection 、Iterator 、ResultSet 等类型添加了别名。
public class TypeAliasRegistry {
private final Map<String, Class<?>> typeAliases = new HashMap<>();
//查找指定包下的superType 类型类
public void registerAliases(String packageName, Class<?> superType) {}
//
public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
}
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);
}
}
资源加载
ClassloaderWrapper
在MyBatis 的IO包中封装了ClassLoader
以及读取资源文件的相关API。在IO 包中提供的ClassLoaderWrapper
是一个ClassLoader
的包装器,其中包含了多个ClassLoader
对象。通过调整多个类加载器的使用顺序, ClassLoaderWrapper
可以确保返回给系统使用的是正确的类加载器。使用ClassLoaderWrapper
就如同使用一个ClassLoader
对象,ClassLoaderWrapper
会按照指定的顺序依次检测其中封装的ClassLoader
对象,并从中选取第一个可用的ClassLoader 完成相关功能。
ResolverUtil
ResolverUtil
可以根据指定的条件查找指定包下的类,其中使用的条件由Test
接口表示。ResolverUtil 中使用classLoader
字段( ClassLoader
类型)记录了当前使用的类加载器,默认情况下,使用的是当前线程上下文绑定的ClassLoader
,我们可以通过setClassLoader()
方法修改使用类加载器。
MyBatis 提供了两个常用的Test
接口实现,分别是IsA
和AnnotatedWith
。IsA
用于检测类是否继承了指定的类或接口, AnnotatedWith
用于检测类是否添加了指定的注解。也可以自己实现Test 接口,实现指定条件的检测。
public class ResolverUtil<T> {
public interface Test {
boolean matches(Class<?> type);
}
public static class IsA implements Test {
private Class<?> parent;
Returns true if type is assignable to the parent type supplied in the constructor. */
@Override
public boolean matches(Class<?> type) {
return type != null && parent.isAssignableFrom(type);
}
}
public static class AnnotatedWith implements Test {
private Class<? extends Annotation> annotation;
@Override
public boolean matches(Class<?> type) {
return type != null && type.isAnnotationPresent(annotation);
}
}
}
DataSource
MyBatis 提供了两个javax.sql.DataSource
接口实现,分别是PooledDataSource
和UnpooledDataSource
。Mybatis 使用不同的DataSourceFactory
接口实现创建不同类型的DataSource
。
DataSourceFactory
DataSourceFactory
接口扮演工厂接口的角色。UnpooledDataSourceFactory
和PooledDataSourceFactory
则扮演着具体工厂类的角色。
binding 模块
SqlSession.queryForObject
方法的第一个参数是 SQL id
,如果不存在,会在执行SqlSession.queryForObject
才会抛出异常。MyBatis 提供了binding
模块用于解决上述问题,定义一个接口(Mapper
接口),不需要继承任何其他接口,而且开发人员不需要提供该接口的实现。
该Mapper
接口中定义了SQL 语句对应的方法,这些方法在MyBatis 初始化过程中会与映射配置文件中定义的SQL 语句相关联。如果存在无法关联的SQL 语句,在MyBatis 的初始化节点就会抛出异常。我们可以调用Mapper 接口中的方法执行相应的SQL 语句,这样编译器就可以帮助我们提早发现上述问题。
public interface BlogMapper {
Blog selectBlog (int i); //在映射配置文件中存在一个< select>节点, id 为” selectBlog ”
}
//首先,获取BlogMapper 对应的代理对象
BlogMapper mapper= session.getMapper(BlogMapper.class);
//调用Mapper 接口中定义的方法执行对应的SQL 语句
Blog blog = mapper.selectBlog(l) ;
MapperRegistry & MapperProxyFactory
MapperRegistry
是Mapper
接口及其对应的代理对象工厂的注册中心。Configuration
是MyBatis 全局性的配置对象,在MyBatis 初始化的过程中,所有配置信息会被解析成相应的对象并记录到Configuration
对象中,Configuration.mapperRegistry
字段,它记录当前使用的MapperRegistry
对象。
public class MapperRegistry {
//Configuration 对象, MyBatis 全局唯一的配置对象,其中包含了所有配置信息
private final Configuration config;
//记录了Mapper 接口与对应MapperProxyFactory 之间的关系
//key 是Mapper 接口对应的Class 对象, value 为MapperProxyFactory 工厂对象,可以为Mapper 接口创建代理对象
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
}
在需要执行某SQL 语句时,会先调用MapperRegistry.getMapper()
方法获取实现了Mapper
接口的代理对象,session.getMapper(BlogMapper. class)
方法得到的实际上是MyBatis 通过JDK 动态代理为BlogMapper
接口生成的代理对象。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//动态代理对象。
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory
主要负责创建代理对象。
public class MapperProxyFactory<T> {
//当前MapperProxyFactory 对象可以创建实现了MapperInterface 接口的代理对象,例如BlogMapper
private final Class<T> mapperInterface;
//缓存, key 是Mapperinterface 接口中某方法对应的Method对象, value 是对应的MapperMethod 对象
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//创建MapperProxy 对象,每次调用都会创建新的MapperProxy 对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
MapperProxy
MapperProxy
实现了lnvocationHandler
接口,的实现是代理对象的核心逻辑。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Throwable t) {
lookup = null;
}
}
lookupConstructor = lookup;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如采目标方法继承自Object ,则直接调用目标方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
//针对Java7 以上版本对动态类型语言的支持
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//从缓存中获取MapperMethod 对象,如采缓存中没有,则创建新的MapperMethod 对象并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行SQL 语句
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
MapperMethod
MapperMethod
中封装了Mapper
接口中对应方法的信息,以及对应SQL 语句的信息。可以将MapperMethod
看作连接Mapper
接口以及映射配置文件中定义的SQL 语句的桥梁。
public class MapperMethod {
//记录了SQL 语句的名称和类型
private final SqlCommand command;
//Mapper 接口中对应方法的相关信息
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public static class SqlCommand {
//记录了SQL 语句的名称,
private final String name;
//记录了SQL 语句的类型。SqlCommandType 是枚举类型,有效取值为UNKNOWN 、INSERT 、UPDATE 、DELETE 、SELECT 、FLUSH 。
private final SqlCommandType type;
}
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
}
}
ParamNameResolver
在MethodSignature
中, 会使用ParamNameResolver
处理Mapper
接口中定义的方法的参数列表。ParamNameResolver
使用name
字段( SortedMap<Integer, String>
类型)记录了参数在参数列表中的位置索引与参数名称之间的对应关系,其中key
表示参数在参数列表中的索引位置,value
表示参数名称,参数名称可以通过@Param
注解指定,如果没有指定@Param
注解,则使用参数索引作作为其名称。如果参数列表中包含RowBounds
类型或ResultHandler
类型的参数,则这两种类型的参数并不会被记录到name
集合中,这就会导致参数的索引与名称不一致,例如, method(int a, RowBounds rb, int b)
方法对应的names 集合为 { {0,"0"},{2,"1"}}
。
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
/**
aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
*/
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
}
MethodSignature
MethodSignature
也是MapperMethod
中定义的内部类,其中封装了Mapper
接口中定义的方法的相关信息。
public static class MethodSignature {
private final boolean returnsMany;//返回值类型是否为Collection 类型或是数组类型
private final boolean returnsMap;//返回值类型是否为Map 类型
private final boolean returnsVoid;//返回值类型是否为VO 工d
private final boolean returnsCursor;//返回值是否为Cursor 类型
private final boolean returnsOptional;//
private final Class<?> returnType;//返回值类型
private final String mapKey;//如果返回值类型是Map ,则该字段记录了作为key 的列名
private final Integer resultHandlerIndex;//用来标记该方法参数列表中ResultHandler 类型参数的位置
private final Integer rowBoundsIndex;//用来标记该方法参数列表中RowBounds 类型参数的位置
private final ParamNameResolver paramNameResolver;//
}
execute()方法
MapperMethod
中最核心的方法是execute()
方法,它会根据SQL 语句的类型调用SqISession
对应的方法完成数据库操作.
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
//使用ParamNameResolver处理args[]数组(用户传入的实参列表),将用户传入的实参与指定参数名称关联起来
Object param = method.convertArgsToSqlCommandParam(args);
//会根据method 字段中记录的方法的返回值类型对结果进行转换
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
当执行INSERT
、UPDATE
、DELETE
类型的SQL 语句时,其执行结果都需要经过MapperMethod.rowCountResult
方法处理。SqISession
中的insert
等方法返回的是int
值,rowCountResult
方法会将该int
值转换成Mapper
接口中对应方法的返回值。
如果Mapper
接口中定义的方法准备使用ResultHandler
处理查询结果集,则通过MapperMethod.executeWithResultHandler
方法处理。
缓存模块
MyBatis 中的缓存是两层结构的,分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是Cache
接口的实现。
附录
中文资料网站:https://mybatis.net.cn/index.html